console.clear();
const { logger } = require('./logSystem.js');
world.say_ = world.say;
world.say = function (...args) {
    console.log("[World Announcement] ", ...args);
    world.say_(...args);
}

const Version = "V86"
const containerId = Date.now();
logger.log(`[Info] Container<${containerId}> started in Version<${Version}>`);
logger.getList({
    start: 0,
    end: Date.now() - 3 * 30 * 24 * 60 * 60 * 1000
}).then(async (data) => {
    for (let i = 0; i < data.length; i++) {
        await logger._storage.remove(data[i].timestamp.toString());
        await sleep(100);
    }
});


// world.userOBB = true
//704:https://dao3.fun/edit/aa1df469f437276c8c82?p=fc3ed2c343ba413293c346dc1a02f9c9
//require('./npc.js')
require('./business.js')
require('./xqn sql.js')
require('./GameUI.js')
//require('./Mail_system.js')

const G = {
    admin: {
        '13162330': '飞跃的流星',
        /*'12976983': 'cuqal',*/
        '78443': '爱摆烂咕咕的屑全能.bushi',
        /*  '12998600': "流璃awa",
          '50427795': '白玄',*/
        '455': '吉吉喵',
        '250': '没尾巴的鲸鱼',
        '302445821485895': "灵境"
    },
    pp: {
        '78443': '爱摆烂咕咕的屑全能.bushi'
    }
}

const SE = {
    explode: { sample: 'audio/explode.mp3' },
    peace: { sample: 'audio/peace.mp3' },
    peace2: { sample: 'audio/peace2.mp3' },
    mask: { sample: 'audio/masquerade.mp3' },
    fight: { sample: 'audio/fight.mp3' },
}

const bounceSound = {
    sample: "audio/机枪射击.mp3", // 声音文件
    radius: 20, // 声音范围
    gain: 1, // 音量增益
    pitch: 1, // 音高调节
    gainRange: 0, // 音量增益方差
    pitchRange: 0, // 音高增益方差
};

const gs = {
    sample: "audio/高斯来复枪射击.mp3", // 声音文件
    radius: 20, // 声音范围
    gain: 1, // 音量增益
    pitch: 1, // 音高调节
    gainRange: 0, // 音量增益方差
    pitchRange: 0, // 音高增益方差
};

const Errors = new Set()
var Revive = {}

const TempAcc = ['_dmgAcc', '_healAcc', '_reviveAcc', '_sellAcc', '_earnAcc', '_fixAcc', '_deployAcc', '_killAcc']

global.bigBoss = {}
var bigBossBattle = false
var Difficulty = 1
var visitCnt = 0
global.$ = world.querySelectorAll
const DEAD_REVIVE_SEC = 15//test
const MeshScale = [0.0625, 0.0625, 0.0625]
const Phold = spawn('', [0, 0, 0])
const SPAWN_POINT = new GameVector3(98, 36, 198)
var MAP = {}
var Prey = []
var deadList = {}
var statList = {}
const AlienName = '"异星舰队"'//外星人名称
const bossReward1 = 0;

const AlienDroneRate = 0.01
const AlienBombRate = AlienDroneRate + 0.04
const AlienGunRate = AlienBombRate + 0.1

const TempStats = ['ammo', 'contrib', 'hp', 'bossReward', 'freeRevive'].concat(...TempAcc)

const IDLE_MS = 120000
const FactionR = '钢铁之心'
const FactionG = '守护者联盟'

const LAVA01 = voxels.id('lava01')
const LAVA02 = voxels.id('lava02')
const DeployQuota = 150
const DeployIndiQuota = 5
const Deployments = []
let wxrname = '��异星舰队'
let dxsj = 999
const P_YELLOW = new GameRGBColor(10, 10, 2)
const P_CYAN = new GameRGBColor(2, 10, 10)
const P_MAGENTA = new GameRGBColor(10, 2, 10)
const P_RED = new GameRGBColor(10, 2, 2)
const P_GREEN = new GameRGBColor(2, 10, 2)
const P_BLUE = new GameRGBColor(2, 2, 10)
const P_WHITE = new GameRGBColor(10, 10, 10)
const BossName = '路西法'
const p_yellow_color = [
    P_YELLOW, P_YELLOW, P_YELLOW, P_YELLOW, P_YELLOW,
]

const p_white_color = p_yellow_color.map(e => P_WHITE)
const p_cyan_color = p_yellow_color.map(e => P_CYAN)
const p_magenta_color = p_yellow_color.map(e => P_MAGENTA)
const p_red_color = p_yellow_color.map(e => P_RED)
const p_green_color = p_yellow_color.map(e => P_GREEN)
const p_blue_color = p_yellow_color.map(e => P_BLUE)
const clr = [p_yellow_color, p_cyan_color, p_magenta_color, p_red_color, p_green_color, p_blue_color, p_white_color]

const minBorder = -40
const maxBorder = 256 - minBorder
const maplen = maxBorder - minBorder

// 每秒检查一次



function log(...a) {
    // console.log(...a)
}
global.log = log

const GLITCH = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split('')
    .concat('zero', 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine')
    .concat(`add
subtract
multiply
divide
equal
exclamation_mark
question_mark
ampersand
asterisk
at
backslash
bracket_close
bracket_open
caret
colon
comma
dollar
greater_than
less_than
paren_open
paren_close
percent
period
pound
quotation_mark
semicolon
slash
tilde`.split('\n')).map(e => voxels.id(e))

const GRAD_CERT = 50 //test
const JOB_TRANSFER = 10 //test
const FEW_PEOPLE = 5

const CyanColor = new GameRGBColor(0, 0.92, 0.92)
const GreenColor = new GameRGBColor(0, 0.92, 0)
const WhiteColor = new GameRGBAColor(1, 1, 1, 1)
const TransColor = new GameRGBAColor(0.3, 0.3, 0.3, 1)
const RedColor = new GameRGBAColor(0.7, 0.1, 0.1, 1)
const birdsEyeC = [128, 100, 128]
const birdsEyeHC = [118, 246, 128]
const birdsEyeLC = [118, 0, 129]
const birdsEyeNW = [255, 100, 255]
const birdsEyeW = [255, 100, 128]
const birdsEyeE = [0, 100, 128]
const birdsEyeN = [128, 100, 255]
const birdsEyeS = [128, 100, 0]
const birdsEyeChinese = [160, 60, 190]
const birdsEyeSeaside = [96, 92, 99]
const birdsEyeVillage = [239, 81, 127]

const BOSS_EDU_NUM = 500
const USER_EMISSIVE = 0.006
const WALK_SPEED = 0.22
const WALK_ACCEL = 0.19
const RUN_SPEED = 0.4
const RUN_ACCEL = 0.35
const WHITE = new GameRGBColor(1, 1, 1)
const RED = new GameRGBColor(1, 0.4, 0.4)
const BLUE = new GameRGBColor(0.4, 0.4, 1)

const W_DEF = 0.4
const W_PARRY = 0.15
const W_PROTECT_RADIUS = 12
const T_MONOPOLY = 8
const T_TRANSFER_ITEM = 20
const S_DMG_BUFF = 2
const W_DMG_BUFF = 3
const M_ITEM_BUFF = 2
const S_DMG_CONTRIB = 0.32
const W_DMG_CONTRIB = 0.25
const W_HURT_CONTRIB = 2
const M_HEAL_CONTRIB = 0.6
const M_REVIVE_CONTRIB = 500
const T_SELL_CONTRIB = 0.3
const E_REPAIR_CONTRIB = 0.9
const E_DEPLOY_CONTRIB = 0.2
const FIX_CITY_CONTRIB = 400
const ACK_FIX_CONTRIB = 50
const WinContrib = 1000
const BossContrib = 3000
const Name = '路西法\0'

const FactionLv = 10

const T_PROFIT = 0.015

const Choice = {
    CLOSE: 2,
}

var DeviceNumber = 0

const NextWaveMS = 1000 * 60 * 5//test 8000
const CONTRIB_THRES = 50

const EDU_NUM = 15
const EDU_THRES = 3
const MAX_EDU = 999
const MAX_COIN = 100000000
const MAX_EXP = 100000000

const CURRENCY = '夸克'
const CAPACITY = 25
const PlayerProp = {
    'radius': '射程',
    'atk': '本能',
    'def': '防御',
    'crt': '暴击率',
    'crtdmg': '暴击倍数',
    'hpm': '生命值',
    'mpm': '魔力值',
    'capacity': '背包容量',
    'title': '获得称号',
    'luck': '运气',
    'faction': '派系',
}

const WEARABLES = {
    "工人衣服": { "offset": [0, -0.1, 0], "rot": [0, 2, 0], "scale": [0.45, 0.25, 0.34], "part": "torso" },
    "工人帽子": { "offset": [0, 0.9, -0.05], "rot": [0, 2, 0], "scale": 0.72, "part": "head" },
    "工具箱": { "offset": [-0.1, -0.35, -0.05], "rot": [0.9, -2, 1], "scale": 0.3, "part": "righthand" },
    "雷电工人衣服": { "offset": [0, -0.1, 0], "rot": [0, 2, 0], "scale": [0.45, 0.25, 0.34], "part": "torso" },
    "雷电工人帽子": { "offset": [0, 0.9, -0.05], "rot": [0, 2, 0], "scale": 0.72, "part": "head" },
    "雷电工具箱": { "offset": [-0.1, -0.35, -0.05], "rot": [0.9, -2, 1], "scale": 0.3, "part": "righthand" },
    "牛角工人帽": { "offset": [0, 1, 0], "rot": [0, 2, 0], "scale": 0.72, "part": "head" },
    "钳虫扳手": { "offset": [0, -0.15, 0], "rot": [0.9, -2, 1], "scale": 0.3, "part": "righthand" },
    "医生服": { "offset": [0, -0.15, 0], "rot": [0, 2, 0], "scale": [0.45, 0.35, 0.43], "part": "torso" },
    "医生帽": { "offset": [0, 0.8, -0.1], "rot": [0, 2, 0], "scale": 0.97, "part": "head" },
    "皇家医生服": { "offset": [0, -0.15, 0], "rot": [0, 2, 0], "scale": [0.45, 0.35, 0.43], "part": "torso" },
    "皇家医生帽": { "offset": [0, 0.8, -0.1], "rot": [0, 2, 0], "scale": 0.97, "part": "head" },
    "牛角冠帽": { "offset": [0, 1, 0], "rot": [0, 2, 0], "scale": 0.97, "part": "head" },
    "医药箱": { "offset": [-0.01, -0.35, -0.05], "rot": [1.1, 2, 1], "scale": 0.4, "part": "righthand" },
    "牛角针筒": { "offset": [-0.01, -0.25, 0.15], "rot": [-1.1, -2, 1], "scale": 0.4, "part": "righthand" },
    "医疗手炮": { "offset": [-0.01, -0.25, 0.15], "rot": [-1.1, -2, 1], "scale": 0.4, "part": "righthand" },
    "士兵衣服": { "offset": [0, -0.05, 0], "rot": [0, 2, 0], "scale": [0.55, 0.3, 0.45], "part": "torso" },
    "士兵帽": { "offset": [0, 0.5, 0.05], "rot": [0, 2, 0], "scale": [0.9, 0.9, 0.85], "part": "head" },
    "黄金衣服": { "offset": [0, -0.05, 0], "rot": [0, 2, 0], "scale": [0.55, 0.3, 0.45], "part": "torso" },
    "黄金帽": { "offset": [0, 0.5, 0.05], "rot": [0, 2, 0], "scale": [0.9, 0.9, 0.85], "part": "head" },
    "追猎护甲": { "offset": [0, -0.05, 0], "rot": [0, 2, 0], "scale": [0.45, 0.3, 0.45], "part": "torso" },
    "追猎头盔": { "offset": [0, 1.2, 0.05], "rot": [0, 2, 0], "scale": [0.9, 0.9, 0.85], "part": "head" },
    "牛角士兵帽": { "offset": [0, 0.75, 0.05], "rot": [0, 2, 0], "scale": [0.9, 0.9, 0.85], "part": "head" },
    "守卫者盔甲": { "offset": [0, -0.1, 0.05], "rot": [0, 2, 0], "scale": [1, 0.4, 0.55], "part": "torso" },
    "守卫者头盔": { "offset": [0, 0.4, -0.35], "rot": [0, 2, 0], "scale": [1.2, 1.2, 1.2], "part": "head" },
    "火焰盔甲": { "offset": [0, -0.1, 0.05], "rot": [0, 2, 0], "scale": [1, 0.4, 0.6], "part": "torso" },
    "火焰头盔": { "offset": [0, 0.4, -0.35], "rot": [0, 2, 0], "scale": [1.2, 1.2, 1.2], "part": "head" },
    "火焰盾牌": { "offset": [0.25, -0.15, 0.2], "rot": [-12, 5, -16], "scale": 0.5, "part": "lefthand" },
    "火龙刀": { "offset": [0, -0.42, 0.8], "rot": [-8, 2, 0], "scale": 0.8, "part": "righthand" },
    "烈火头盔": { "offset": [0, 0.4, -0.35], "rot": [0, 2, 0], "scale": [1.2, 1.2, 1.2], "part": "head" },
    "烈火铠甲": { "offset": [0, -0.1, 0.05], "rot": [0, 2, 0], "scale": [1, 0.4, 0.6], "part": "torso" },
    "冰晶头": { "offset": [0, 0.4, -0.35], "rot": [0, 2, 0], "scale": [1.2, 1.2, 1.2], "part": "head" },
    "冰晶甲": { "offset": [0, -0.1, 0.05], "rot": [0, 2, 0], "scale": [1, 0.4, 0.6], "part": "torso" },
    "牛角头盔": { "offset": [0, 0.8, -0.1], "rot": [0, 2, 0], "scale": 1.25, "part": "head" },
    "防卫枪": { "offset": [0, -0.35, 0.15], "rot": [1, 1, 2], "scale": 0.22, "part": "righthand" },
    "守护剑": { "offset": [0, -0.42, 0.65], "rot": [-8, 2, 0], "scale": 0.4, "part": "righthand" },
    "守卫盾牌": { "offset": [0.25, -0.15, 0.2], "rot": [-12, 5, -16], "scale": 0.3, "part": "lefthand" },
    "商人衣服": { "offset": [0, -0.25, 0.02], "rot": [0, 2, 0], "scale": [0.54, 0.34, 0.43], "part": "torso" },
    "商人帽": { "offset": [0, 0.45, -0.1], "rot": [0, 2, 0], "scale": [1, 0.9, 1], "part": "head" },
    "商人背包": { "offset": [0, 0.2, -0.55], "rot": [0, 2, 0], "scale": 0.6, "part": "torso" },
    "海洋商人服": { "offset": [0, -0.25, 0.02], "rot": [0, 2, 0], "scale": [0.54, 0.34, 0.43], "part": "torso" },
    "海洋商人帽": { "offset": [0, 0.45, -0.1], "rot": [0, 2, 0], "scale": [1, 0.9, 1], "part": "head" },
    "海洋商人背包": { "offset": [0, 0.2, -0.55], "rot": [0, 2, 0], "scale": 0.6, "part": "torso" },
    "异界权杖": { "offset": [0, -0.15, 0.15], "rot": [-2, 0, -2], "scale": 0.6, "part": "righthand" },
    "星月权杖": { "offset": [0, -0.15, 0.15], "rot": [-2, 0, -2], "scale": 0.6, "part": "righthand" },
    "扩容背包lv1": { "offset": [0, 0.1, -0.55], "rot": [0, 2, 0], "scale": 0.6, "part": "torso" },
    "创造者背包": { "offset": [0, 0.1, -0.55], "rot": [0, 2, 0], "scale": 0.6, "part": "torso" },
    "喷气背包": { "offset": [0, 0.2, -0.55], "rot": [0, 2, 0], "scale": 0.6, "part": "torso" },
    "臂章铁心": { "offset": [0.27, 0, 0], "rot": [3.5, 0, 0], "scale": 0.5, "part": "leftShoulder" },
    "大佬臂章": { "offset": [0.27, 0, 0], "rot": [3.5, 0, 0], "scale": 0.5, "part": "leftShoulder" },
    "臂章护联": { "offset": [0.27, 0, 0], "rot": [3.5, 0, 0], "scale": 0.5, "part": "leftShoulder" },
    "手持火箭炮": { "offset": [-0.43, -0.3, -0.15], "rot": [0, 0, 0], "scale": 0.45, "part": "head" },
    "购货单": { "offset": [0, 1.7, 0], "rot": [0, 2, 0], "scale": 0.8, "part": "head" },
    "购货单2": { "offset": [0, 1.7, 0], "rot": [0, 2, 0], "scale": 0.8, "part": "head" },
    "保护盾": { "offset": [0, 2.8, 0], "rot": [0, 2, 0], "scale": 0.5, "part": "torso" },
    "单兵用盾牌": { "offset": [0.5, -0.1, 0.3], "rot": [0, 2, 0], "scale": 0.5, "part": "torso" },
    "残响盾": { "offset": [0.5, -0.1, 0.3], "rot": [0, 2, 0], "scale": 0.5, "part": "torso" },

    "枪-7": { "offset": [-0.02, -0.1, 0.15], "rot": [0, 1, 2], "scale": 0.5, "part": "righthand" },
    "黄金审判者": { "offset": [-0.02, -0.1, 0.4], "rot": [0, 1, 0], "scale": 0.4, "part": "righthand" },
    "异星能量机枪": { "offset": [-0.02, -0.1, 0.4], "rot": [0, 0, 0], "scale": 0.4, "part": "righthand" },
    "能量机枪": { "offset": [-0.02, -0.1, 0.4], "rot": [0, 0, 0], "scale": 0.4, "part": "righthand" },
    "牛角商人帽": { "offset": [0.1, 0.7, -0.2], "rot": [0, 2, 0], "scale": [1, 0.95, 1], "part": "head" },
    "牛角火炮": { "offset": [-0.5, 0, 0.05], "rot": [0, 2, 0], "scale": 0.45, "part": "head" },
    "牛角锤": { "offset": [0, -0.1, 0.45], "rot": [0, 0, -2], "scale": 0.4, "part": "righthand" },
    "火焰锤": { "offset": [0, -0.1, 0.45], "rot": [0, 0, -2], "scale": 0.4, "part": "righthand" },
    "甲虫手杖": { "offset": [0, -0.15, 0.15], "rot": [-2, 0, -2], "scale": 0.6, "part": "righthand" }
}

const PI2 = Math.PI / 2
const PIx2 = Math.PI * 2
const PI4 = Math.PI / 4

const ENEMY = {
    'ufo0': { name: '灭族飞船', size: 3, rotFall: (rotx, PI, rot) => Quat.rotateX(-rotx).rotateY(rot + PI), rot: Math.PI },
    'ufo1': { name: '等离子战机', size: 3, rotFall: (rotx, PI, rot) => Quat.rotateX(-rotx).rotateY(rot + PI), rot: Math.PI },
    'ufo2': { name: '毒蜂轰炸机', size: 5, rotFall: (rotx, PI, rot) => Quat.rotateX(rotx).rotateY(rot + PI), rot: 0 },
    'ufo3': { name: '宇宙海盗', size: 4, rotFall: (rotx, PI, rot) => Quat.rotateZ(rotx).rotateY(rot + PI), rot: -Math.PI / 2 },
    'ufo4': { name: '蛇头轰炸机', size: 3, rotFall: (rotx, PI, rot) => Quat.rotateX(-rotx).rotateY(rot + PI), rot: Math.PI },
    '路西法舰队': { name: '路西法舰队', size: 6, rotFall: (rotx, PI, rot) => Quat.rotateZ(rotx).rotateY(rot + PI), rot: -Math.PI / 2 },
    '1luxifa': { name: '路西法魔王', size: 7, rotFall: (rotx, PI, rot) => Quat.rotateZ(rotx).rotateY(rot + PI), rot: -Math.PI / 2 },
}

const Projectile = {
    'beam0': {},
    'beam1': {},
    'beam11': {},
    '蓝光球': {},
    '红光球': {},
    '大号的导弹': { emissive: 0.05 },
    '深空异骸射流': { emissive: 0.05 },
    '深空异骸飞刃': { emissive: 0.05 },
    '深空异骸飞弹': { emissive: 0.05 },

}

function rotateTo(userPos, targetPos, rot = 0, ratio = 0.5) {
    // var direction = user.position.sub(this.position)
    const dz = targetPos.z - userPos.z
    const dx = targetPos.x - userPos.x
    const tg = Math.atan2(dz, dx)

    if (rot > Math.PI) {
        rot -= PIx2
    }
    else if (rot < -Math.PI) {
        rot += PIx2
    }

    let diff = tg - rot

    if (diff < -Math.PI) {
        diff += PIx2
    }
    else if (diff > Math.PI) {
        diff -= PIx2
    }

    rot += diff * ratio

    return rot
}

var fool_cnt = 0;
global.dialog = dialog
async function dialog(entity = new GameEntity, title, content, o) {
    if (o == undefined) {
        return entity.player.dialog({
            type: GameDialogType.TEXT,
            title: title,
            content: content,
        })
    } else if (typeof o == 'string') {
        return entity.player.dialog({
            type: GameDialogType.INPUT,
            title: title,
            content: content,
            confirmText: o
        })
    } else if (typeof o == 'object') {
        return entity.player.dialog({
            type: GameDialogType.SELECT,
            title: title,
            content: content,
            options: o,
        })
    }
}






global.random = random
function random(min, max, type = 'float') {
    var a = min + Math.random() * (max - min);
    if (type == 'int') {
        a = Math.floor(a);
    };
    return a;
};

global.pmDist = pmDist
function pmDist(pos1, pos2) {
    return new GameVector3(pos1.x, 0, pos1.z).distance(new GameVector3(pos2.x, 0, pos2.z))
}
function find(name) {
    let p = world.querySelectorAll('player').find(e => e.player.name == name);
    if (p) {
        return p
    } else {
        return world.querySelectorAll('player').find(e => e.player.name.includes(name))
    }
}
world.onChat(async ({ entity: user, message }) => {
    user.lastActive = Date.now()
    let entity = user;
    if (isAdmin(user)) {
        if (message.startsWith('$')) {
            let command = -1;
            if (message == '$') {
                let result = await dialog(user, '管理员', '请输入代码', '确认');
                if (!result || result == null) {
                    return;
                }
                command = result;
                try {
                    dialog(user, '运行结果', `~> ${await eval(result)}`)
                } catch (e) {
                    user.player.directMessage(`控制台报错：${e}`)
                }
            } else {
                command = message.slice(1);
                try {
                    dialog(user, '运行结果', `~> ${eval(command)}`)
                } catch (e) {
                    user.player.directMessage(`控制台报错：${e}`)
                }
            }

            if (command === -1) {
                return;
            }
            logger.log(`[Info] GM <${entity.player.userId}>${entity.player.name} used code: ${command}`);
            return;
        }
        if (message === 'wave') {
            nextWaveMS = Date.now()
        }
        else if (message === 'nw') {
            try {
                newWave(1)
            } catch (e) {
                world.say(e.stack)
                logger.log(`[Error] newWave<1>: ${e}`);
            }
        }
        else if (message === 'boss') {
            bigBossBattle = true
            for (const e of world.querySelectorAll('#路西法叛徒-1')) {
                e.meshInvisible = false
                e.enableInteract = true
            }
        }
        else if (message === 'killE') {
            killEnemy()
        }
        else if (message === 'killBoss') {
            const b = world.querySelector('#boss')
            if (b) {
                // b.selfDestruct()
                b.destroyed = true;
            }
        }
        else if (message === 'april') {
            user.msg(fool_cnt)
        }
        else if (message === 'fix') {
            repairCity()
        }
        else if (message === 'difficulty9') {
            Difficulty = AlienGunRate
        } else if (message === 'difficulty1') {
            Difficulty = 1
        } else if (message === 'difficulty5') {
            Difficulty = 0.6
        } else if (message === 'items') {
            for (const p of players()) {
                p.data.item.unshift(Object.keys(ItemDict))
                calcUserProp(p)
            }
        } else if (message === 'coin') {
            for (const p of players()) {
                p.data.coin += 5000
                calcUserProp(p)
            }
        } else if (message === 'edu') {
            for (const p of players()) {
                p.data.edu_score += 40
                calcUserProp(p)
            }
        } else if (message === 'exp') {
            for (const p of players()) {
                p.data.exp += 10000
                calcUserProp(p)
            }
        } else if (message === 'vip') {
            entity.player.color.set(1, 1, 0);
            entity.player.canFly = true;
            entity.player.scale += 1;
        } else if (message === 'restart') {
            restart();
        } else if (message === 'logs') {
            const records = await logger.getList({
                start: Date.now() - 5 * 60 * 60 * 1000
            });
            let result = [];

            records.forEach((record) => {
                //result.push(`[${Date(record.timestamp)}] ${record.content}`);
                const time = new Date(record.timestamp);
                const timeString = `${time.getUTCFullYear()}-${time.getUTCMonth() + 1}-${time.getUTCDate()} ${time.getUTCHours()}:${time.getUTCMinutes()}:${time.getUTCSeconds()} GMT`
                result.push(`[${timeString}] ${record.content}`);
            });
            entity.dlgMsg(result.join("\n"));
            return;
        } else if (message === 'hp') {
            if (entity.hpLock) {
                entity.hpLock = false;
                entity.hurt = entity.hurt_;
            } else {
                entity.hpLock = true;
                entity.hurt_ = entity.hurt;
                entity.hurt = () => { };
            }
        } else {
            return;
        }
        logger.log(`[Info] GM <${entity.player.userId}>${entity.player.name} used command: ${message}`);
    }
})

function canHeal(npc) {
    return npc.isNPC
}

function jitter() {
    return (0.9 + Math.random() * 0.2)
}

/** @param user {GameEntity} */
function changeMusic(user) {
    if (isBattling) {
        if (bigBossBattle) { // user.data.exp < 4096
            user.player.music.sample = SE.fight.sample
        } else {
            user.player.music.sample = SE.mask.sample
        }
    } else {
        if (waveCnt & 1) {
            user.player.music.sample = SE.peace2.sample
        } else {
            user.player.music.sample = SE.peace.sample
        }
    }
}

function enemyGuard(user, target, dmg) {
    if (target.guard) {
        user.msg(`${BossName}开启了能量护盾, 普通攻击对它几乎不起作用. 学校的异星使者们似乎知道怎样关掉它`)
        return 1
    }
    return dmg
}

function damageToEnemy(self, target) {
    let dmg
    if (self.role === 'S') {
        dmg = enemyGuard(self, target, S_DMG_BUFF * self.atk)
        if (target.hasTag('子弹抗性')) {
            dmg *= 0.25
        }
        const contrib = dmg * S_DMG_CONTRIB
        self.contrib += contrib
        self.roundContrib += contrib
        self._dmgAcc += dmg
    }
    else if (self.role === 'W') {
        dmg = enemyGuard(self, target, S_DMG_BUFF * self.atk)
        const contrib = dmg * W_DMG_CONTRIB
        self.contrib += contrib
        self.roundContrib += contrib
        self._dmgAcc += dmg
    }
    else {
        dmg = enemyGuard(self, target, self.atk)
    }
    target._attacker = self
    target.hurt(dmg)
}

function turretDamageToEnemy(pilot, deployer, target, ratio = 1) {
    let dmg
    if (pilot.role === 'S') {
        dmg = S_DMG_BUFF * pilot.atk
    } else {
        dmg = pilot.atk
    }
    dmg *= ratio
    dmg = enemyGuard(pilot, target, dmg)

    target._attacker = pilot
    // dmg = 1000//dmg
    target.hurt(dmg)
    const contrib = (dmg * S_DMG_CONTRIB)
    pilot._dmgAcc += dmg
    pilot.contrib += contrib * 0.6
    pilot.roundContrib += contrib
    deployer.contrib += contrib * 0.4
}

function getPlayerRaycast(entity = new GameEntity, type = {}) {
    let angle = entity.player.cameraYaw + Math.PI
    let up = -entity.player.cameraPitch
    let dir = new GameVector3(0, 0, 0);
    dir.x = Math.cos(angle) * Math.cos(up);
    dir.y = Math.sin(up);
    dir.z = Math.sin(angle) * Math.cos(up)
    return world.raycast(entity.position, dir, type)
};

const WEAPON = {
    tbox(self, target, evt, data, repair_range = 1) {
        if (target) {
            if (target.deployer) {
                const dist = self.position.distance(target.position)
                if (dist > self.radius) {
                    self.msg(`离得太远, 无法修理, 还差${Math.ceil(dist - self.radius)}格距离`)
                }
                else if (target.noRepair) {
                    self.msg(`此设施无法维修`)
                }
                else if (target.hp < target.maxHp && !target.isEnemy) {
                    if (self.ammo > 0) {
                        const hp = (self.atk * jitter()) | 0
                        target.hp = Math.min(target.maxHp, target.hp + hp)
                        self.ammo -= data.spend
                        target.particleUp(p_blue_color)
                        const contrib = hp * M_HEAL_CONTRIB * 0.2
                        self.contrib += contrib
                        self.roundContrib += contrib
                        // self._healAcc += hp
                        self.msg(`${target.interactHint}被你修复了${hp}耐久值, "${ItemDict[data.ammoId].name}"剩余${self.ammo}%`)
                    } else {
                        self.msg(`需要重新填装"${ItemDict[data.ammoId].name}"才能继续修理设施`)
                    }
                } else {
                    self.msg(`设施不需要修理`)
                }
            }
        }
        else if (evt.raycast.hitVoxel) {
            const p = evt.raycast.voxelIndex
            if (self.ammo > 0) {
                const dist = self.position.distance(p.add({ x: 0.5, y: 0.5, z: 0.5 }))
                if (dist > self.radius) {

                    self.msg(`离得太远, 无法施工, 还差${Math.ceil(dist - self.radius)}格距离`)
                }
                else if (self.player.crouchButton) {
                    if (isAdmin(self)) {
                        cubeKill(p.x, p.y, p.z, 1)
                    }
                } else {
                    let score
                    if (repair_range < 0) {
                        score = 0
                    } else if (repair_range) {
                        score = cubeRepair(p.x, p.y, p.z, repair_range)
                    } else {
                        score = crossRepair(p.x, p.y, p.z)
                    }
                    if (score) {
                        const contrib = score * E_REPAIR_CONTRIB
                        self.contrib += contrib
                        self.roundContrib += contrib
                        self._fixAcc += score
                        self.ammo -= data.spend
                        self.msg(`你修复了${score}格, "${ItemDict[data.ammoId].name}"剩余${self.ammo}%`)
                    } else {
                        self.msg('这里已经修好')
                    }
                }
            } else {
                self.msg(`需要重新填装"${ItemDict[data.ammoId].name}"才能继续施工`)
            }
        }
    },
    tboxa(...a) { WEAPON.tbox2(...a) },
    tboxD(...a) { WEAPON.tbox2(...a) },
    tboxd(...a) { WEAPON.tbox2(...a) },

    tbox2(self, target, evt, data) {
        WEAPON.tbox(self, target, evt, data, Math.max(4, Math.min(self.radius / 5, 8)));
    },
    mboxa(...a) { WEAPON.mbox(...a) },
    mboxA(...a) { WEAPON.mbox(...a) },

    mbox2(self, target, evt, data) {
        WEAPON.mbox(self, target, evt, data)
    },
    bzk1a(...a) { WEAPON.bzk1(...a) },
    bzk1(self, target, evt, data) {
        if (!target) return
        if (target.hp <= 0 || !target.enableDamage || !target.isEnemy) return
        if (self.ammo > 0) {
            const now = Date.now()
            if (now - self._gunClock > data.delay) {
                self._gunClock = now
                missile({ deployer: self, pilot: self, position: self.position }, target, '大号的导弹', 2, 'pBullet', ItemDict.bzk1)
                self.ammo -= data.spend
                self.msg(`弹药剩余${self.ammo}%`)
            }
        } else {
            self.msg(`需要重新填装"${ItemDict[data.ammoId].name}"才能继续射击`)
        }
    },
    roda() {

    },
    async shH1(self, target, evt, data) {

        let t = 0;
        while (self.player.crouchButton) {
            t += 1;
            await sleep(100);
            if (t >= 10) {
                break;
            }
        }
        if (t < 10) {
            return;
        }
        if (self.ammo > 5) {
            self.ammo -= 5
            self.msg(`能量电池剩余${Math.ceil(self.ammo)}%`)
        } else {
            self.msg(`需要重新填装"${ItemDict[data.ammoId].name}"才能继续战斗`)
            return;
        }

        let e = world.createEntity({
            mesh: 'mesh/火球.vb',
            position: self.position,
            collides: false,
            gravity: false,
            meshScale: new GameVector3(0.2, 0.2, 0.2),
        })
        e.addTag('dun')
        e.particleRate = 100;
        e.particleLifetime = 3;
        e.particleNoise *= 5;
        e.particleColor = [new GameRGBColor(1, 1, 0), new GameRGBColor(1, 0, 0)];

        for (let i = 0; i < 5; i++) {
            e.meshScale = e.meshScale.scale(1.2)
            world.querySelectorAll('.eBullet,.eDrone,.ship').forEach((d) => {
                if (d.position.distance(e.position) <= e.bounds.x * 2) {
                    if (!d.hasTag('ship') && d.maxHp <= 1000) {//无法撼动boss
                        let dir = d.position.sub(e.position);
                        dir = dir.scale(1 / dir.mag());
                        d.velocity.copy(dir.scale(5));
                    }
                    if (d.enableDamage) {
                        let dmg = Math.ceil(30 + self.lv() * 2)
                        d.hurt(dmg);
                        d._attacker = self
                        self.contrib += dmg * W_DMG_CONTRIB;
                    }
                }
            })
            await sleep(160);
        }
        e.destroy();
    },
    sw1a(...a) { WEAPON.sw1(...a) },
    swH1(...a) {
        WEAPON.sw1(...a);
    },
    sw1(self, target, evt, data) {
        const now = Date.now()
        if (now - self._gunClock > data.delay) {
            self._gunClock = now
        } else {
            return;
        }
        if (self.player.jumpButton) {
            self.velocity.x += self.player.facingDirection.x * 2;
            self.velocity.z += self.player.facingDirection.z * 2;

        }

        if (target || data.id == 'swH1') {
            if (self.ammo > 0) {
                self.ammo -= data.spend
                self.msg(`能量电池剩余${Math.ceil(self.ammo)}%`)
            } else {
                self.msg(`需要重新填装"${ItemDict[data.ammoId].name}"才能继续战斗`)
                return;
            }
        } else {
            return;
        }

        if (data.id == 'swH1') {
            (async function () {
                let e = world.createEntity({
                    mesh: 'mesh/火球.vb',
                    position: self.position,
                    collides: false,
                    gravity: false,
                    meshScale: new GameVector3(0.05, 0.05, 0.05),
                })
                e.addTag('dun')
                e.particleRate = 50;
                e.particleLifetime = 3;
                e.particleSize *= 10;
                e.particleNoise *= 2;
                e.particleColor = [new GameRGBColor(1, 1, 0), new GameRGBColor(1, 0, 0)];
                let rrr = getPlayerRaycast(self, {});
                e.velocity.copy(rrr.direction.scale(4));
                for (let i = 0; i < 10; i++) {
                    e.meshScale = e.meshScale.scale(1.2);
                    let dmg = Math.ceil((30 - i) * ((self.lv() * 2 + 20) / 30));
                    world.querySelectorAll('.eDrone,.ship').forEach((d) => {
                        if (d.position.distance(e.position) <= e.bounds.x * 2 && d.hp > 0) {
                            d.hurt(dmg);
                            d._attacker = self
                            self.contrib += dmg * W_DMG_CONTRIB;
                        }
                    })
                    await sleep(100);
                }
                e.destroy();
            })();
        }
        if (!target) {
            return;
        }
        if (target.isEnemy && target.hp > 0) {
            const dist = self.position.distance(target.position)
            if (dist > self.radius) {
                self.msg(`目标在攻击范围以外, 还差${Math.ceil(dist - self.radius)}格距离`)
            } else {
                damageToEnemy(self, target)
            }
            // if (target.isUfo) {
            //     self.msg(`普通近战武器无法有效伤害飞行目标`)
            // } else {
            // }
        }
    },
    async gun3(self, target, evt, data, shootD = true) {
        WEAPON.gunG(self, target, evt, data, true)
    },

    async gun4(self, target, evt, data, shootD = true) {
        WEAPON.gunG(self, target, evt, data, true)
    },
    async gunG(self, target, evt, data, shootD = false) {
        let d = self.player.cameraDistance;
        if (d <= 0) {
            return;
        }
        for (let i = 0; i < 10; i++) {
            if (!self.player.action0Button) {
                return self.player.cameraDistance = d;
            }
            self.player.cameraDistance -= d / 10;
            await sleep(100);
        }
        let e = world.createEntity();
        e.addTag(self.player.userId);
        e.mesh = `mesh/金条.vb`;
        e.meshScale = new GameVector3(0, 0, 0);
        e.collides = false
        e.gravity = false;
        e.meshInvisible = true;
        e.particleColor = new GameRGBColor(1, 1, 0)
        e.particleRate = 12;
        e.particleSize *= 10;
        e.particleSize = 5;
        e.particleLifetime = 1;
        if (data.id != 'gunG') {
            e.particleRate /= 2;
            e.particleSize *= 1.5;
            e.particleColor = new GameRGBColor(0.2, 0.2, 1);
        }
        let i = 0;
        let fp = self.position.clone();
        while (self.player.action0Button) {
            if (self.hp <= 0) {
                break;
            }
            e.position.copy(self.position);
            let r = getPlayerRaycast(self, { ignoreSelector: `.${self.player.userId},.Deployment` })
            e.particleVelocity = r.direction.scale(300);
            self._gunClock = 0;
            if (WEAPON.gun1(self, r.hitEntity, evt, data) == '重新装填') {
                break;
            }
            if (shootD) {
                if (Math.abs(self.player.cameraPitch) < Math.PI / 2 - 0.1) {
                    self.player.setCameraPitch(self.player.cameraPitch - 0.01)
                }
                self.player.setCameraYaw(self.player.cameraYaw + random(-0.02, 0.02))
            } else {
                self.position.copy(fp);
            }
            e.sound(bounceSound);
            await sleep(data.delay);
            i++;
            if (i > 120) {
                self.player.directMessage(`枪口过热请重新准备射击！`)
                break;
            }
        };
        e.destroy();
        self.player.cameraDistance = d;
    },
    gun2(self, target, evt, data) {
        WEAPON.gun1(self, target, evt, data)
    },
    gun1(self, target, evt, data) {
        if (self.player.crouchButton) {
            if (isAdmin(self)) {
                const pos = evt.raycast.voxelIndex
                sphereExplode(0, pos.x, pos.y, pos.z, 0)
            }
        }
        if (data.id == 'gunG' || data.id == 'gun3' || data.id == 'gun4' || target) {
            if (self.ammo > 0) {

                self.ammo -= data.spend
                self.msg(`弹药剩余${self.ammo}%`);
            } else {
                self.msg(`需要重新填装"${ItemDict[data.ammoId].name}"才能继续射击`)
                return '重新装填'
            }
        } else {
            return;
        }
        const now = Date.now()
        if (now - self._gunClock > data.delay) {
            self._gunClock = now
            if (!target || target.hp <= 0 || !target.enableDamage || !target.isEnemy) {
            } else {
                const dist = self.position.distance(target.position)
                if (dist > self.radius) {
                    self.msg(`目标在射程以外, 还差${Math.ceil(dist - self.radius)}格距离`)
                } else {
                    damageToEnemy(self, target)
                }
            }
        } else {

        }


    },
    mbox(self, target, evt, data) {
        if (!target) return

        const dist = self.position.distance(target.position)
        if (self.player.crouchButton) {
            if (isAdmin(self)) {
                target.hurt(30)
            }
        }
        else if (dist > self.radius) {
            self.msg(`离得太远, 治疗不到, 还差${Math.ceil(dist - self.radius)}格距离`)
        }
        else if (!target.isPlayer && canHeal(target)) {
            if (target.hp < target.maxHp) {
                if (self.ammo > 0) {
                    const hp = (self.atk * jitter()) | 0
                    target.hp += hp
                    self.ammo -= data.spend
                    target.particleUp(p_green_color)
                    const contrib = hp * M_HEAL_CONTRIB
                    self.contrib += contrib
                    self.roundContrib += contrib
                    self._healAcc += hp
                    self.msg(`${target.interactHint}被你恢复了${hp}生命, "${ItemDict[data.ammoId].name}"剩余${self.ammo}%`)
                } else {
                    self.msg(`需要重新填装"${ItemDict[data.ammoId].name}"才能继续实施治疗`)
                }
            } else {
                self.msg(`对方不需要治疗`)
            }
        }
        else {
            if (target.isPlayer) {
                if (!target.player.dead) {
                    if (target.hp < target.maxHp) {
                        if (self.ammo > 0) {
                            const hp = (self.atk * jitter()) | 0
                            target.hp += hp
                            self.ammo -= data.spend
                            const contrib = hp * M_HEAL_CONTRIB
                            target.particleUp(p_green_color)
                            self.contrib += contrib
                            self.roundContrib += contrib
                            self._healAcc += hp
                            self.msg(`${target.name()}被你恢复了${hp}生命, "${ItemDict[data.ammoId].name}"剩余${self.ammo}%`)
                            target.msg(`${self.name()}帮你恢复了${hp}生命`);
                            world.querySelectorAll('player').forEach((p) => {
                                if (p.hp > 0 && !p.player.dead && p.position.distance(target.position) <= 5) {
                                    p.hp = Math.min(Math.round(hp / 5) + p.hp, p.maxHp); // p.hp += Math.round(hp / 5)
                                    self.contrib += Math.round(contrib / 5)
                                    self.roundContrib += Math.round(contrib / 5)
                                    self._healAcc += Math.round(hp / 5);
                                    p.particleUp(p_green_color);
                                }
                            })
                            if (data.id == 'mboxA') {
                                target.particleRate *= 20;
                                target.particleSize = [8, 6, 3, 2, 1];
                                target.particleVelocitySpread = new GameVector3(5, 1, 5);
                                target.particleLifetime = 2;
                                target.particleVelocity = new GameVector3(0, 2.5, 0);
                                target.particleAcceleration = new GameVector3(0, -2, 0);

                            }
                        } else {
                            self.msg(`需要重新填装"${ItemDict[data.ammoId].name}"才能继续实施治疗`)
                        }
                    } else {
                        self.msg(`${target.name()}已经被完全治愈`)
                    }
                } else {
                    self.msg(`${target.name()}已经阵亡, 靠近战友按E施救`);
                }
            }
        }
    },
}

function weaponEffect(user, target, evt, id) {
    const fn = WEAPON[id]
    if (fn) {
        fn(user, target, evt, ItemDict[id])
    } else {
        console.log(id, ' eff missing')
    }
}

function canDisarm(user, device, now = Date.now()) {
    return device.deployer === user || !device.deployer.ready || !device.deployer.isActive(now)
}

function tryDisarm(self, target) {
    if (canDisarm(self, target)) {
        self.menu(`本市目前有(${Deployments.length}/${DeployQuota})个设施, 要拆除${target.interactHint}吗?`, '拆除设施', {
            async 确定() {
                if (target.leave) {
                    if (target.pilot) {
                        target.pilot.msg(`${target.interactHint}被拆除`)
                    }
                    target.leave()
                }
                self.msg(`你拆除了${target.interactHint}`)
                target.destroy()
            },
        })
    } else {
        self.msg(`${target.deployer.name()}仍需要它`)
    }
}

function addProtectIcon(protectee) {
    if (!protectee._protectIcon) {
        protectee._protectIcon = addWear(protectee, '保护盾')
    }
}

function delProtectIcon(protectee) {
    if (protectee._protectIcon) {
        protectee.player.removeWearable(protectee._protectIcon)
        delete protectee._protectIcon
    }
}

const Role = {
    'S': {
        leader: '士兵npc-1',
        name: '士兵',
        desc: `1. 士兵使用武器时, 攻击力和射程都有明显加强\n2. 可以操作各种武器设施`,
        abv: '兵',
        atkName: '射击',
        atk: (user) => (S_DMG_BUFF * user.atk) | 0,
        contrib: 1,
        rank: [
            '步兵',
            '防空兵',
            '特种兵',
            '指挥官',
        ],
        /** @param e {GameInputEvent}*/
        action(e) {//shoot
            const target = e.raycast.hitEntity
            const self = e.entity
            const weaponId = self.data.wear.武器
            if (weaponId) {
                weaponEffect(self, target, e, weaponId)
            }
            else {
                self.msg(`需要装备武器才能攻击`)
            }
        },
        /** 
         * @param customer {GameEntity}
         * @param self {GameEntity}
         * */
        serve(customer, self) {
            customer.msg(`${Role[self.role].name} ${self.name()}暂无服务技能`)
        },
    },
    'E': {
        leader: '工人导师npc-1',
        name: '工程师',
        desc: `工程师装备"工具箱"后会具备以下能力\n1. 按左键可以修复破损的建筑(消耗"工具零件")\n2. 在地图上部署各类设施(消耗相应的设施道具)`,
        abv: '工',
        atkName: '工程力',
        atk: (user) => (0.5 * user.atk) | 0,
        contrib: 1,
        rank: [
            '维修员',
            '工程兵',
            '战地工程师',
            '技术专家',
        ],
        /** @param e {GameInputEvent}*/
        action(e) {//repair
            const target = e.raycast.hitEntity
            const self = e.entity
            const weaponId = self.data.wear.武器

            // if (target && target.deployer) {
            //     tryDisarm(self, target)
            // }
            // else 
            if (weaponId) {
                weaponEffect(self, target, e, weaponId)
            }
            else {
                self.msg(`需要装备"${ItemDict.tbox.name}"才能施工`)
            }
        },
        /** 
         * @param customer {GameEntity}
         * @param self {GameEntity}
         * */
        serve(customer, self) {
            customer.msg(`${Role[self.role].name} ${self.name()}暂无服务技能`)
        },
    },
    'M': {
        leader: '医生导师npc-1',
        name: '医生',
        desc: `医生装备"急救箱"后会具备以下能力\n1. 左键可以帮对方回复生命(消耗"医疗器件")\n2. 可以按E复活阵亡的玩家(消耗"复活装置")`,
        abv: '医',
        atkName: '医术',
        atk: (user) => (0.8 * user.atk) | 0,
        contrib: 1,
        rank: [
            '实习医师',
            '医务兵',
            '战地医生',
            '医官长',
        ],
        /** @param e {GameInputEvent}*/
        action(e) {//medic
            const target = e.raycast.hitEntity
            const self = e.entity
            const weaponId = self.data.wear.武器
            if (weaponId) {
                weaponEffect(self, target, e, weaponId)
            }
            else {
                self.msg(`需要装备"${ItemDict.mbox.name}"才能治疗对方`)
            }
        },
        /** 
         * @param customer {GameEntity}
         * @param self {GameEntity}
         * */
        serve(customer, self) {
            customer.msg(`${Role[self.role].name} ${self.name()}暂无服务技能`)
        },
        /** @param target {GameEntity} */
        ask(target, self) {
            if (target.isPlayer) {
                if (target.player.dead) {
                    if (isWearingMedbox(self)) {
                        if (self.hasItem('r1')) {
                            revive(target)
                            const contrib = M_REVIVE_CONTRIB
                            self.contrib += contrib
                            self.roundContrib += contrib
                            self._reviveAcc += 1
                            target.particleUp(p_white_color)
                            world.say(`${self.name()}对 ${target.name()}成功实施复活手术`)
                            self.loseItem('r1')
                        } else {
                            self.msg(`需要消耗1个"${ItemDict.r1.name}"才能实施复活手术`)
                        }
                    } else {
                        self.msg(`需要装备"${ItemDict.mbox.name}"才能实施复活手术`)
                    }
                }
            }
        },
    },
    'T': {
        leader: '商人导师npc-1',
        name: '商人',
        desc: `1. 其他职业对商人按E就能买走商人的道具\n2. 商人在商店买东西会有较大优惠`,
        abv: '商',
        atkName: '进货价',
        atk: (user) => Math.max((100 - user.atk * 0.8) / 100, 0.4),
        contrib: 1,
        rank: [
            '业余配送员',
            '配送专员',
            '批发商',
            '大型供应商',
        ],
        /** @param e {GameInputEvent}*/
        action(e) {//trade
            const target = e.raycast.hitEntity
            const self = e.entity
            const weaponId = self.data.wear.武器
            if (weaponId) {
                weaponEffect(self, target, e, weaponId)
            }
            else {
                self.msg(`没有装备任何工具或武器`)
            }
        },
        /** 
         * @param customer {GameEntity}
         * @param self {GameEntity}
         * */
        async serve(customer, self) {
            if (!self.player.dead) {
                if (customer.role === 'T') {
                    await customer.dlgMsg(`同行之间禁止交易, 商人只能去商店进货哦`)
                } else {
                    await salesMenu(self, customer)
                }
            } else {
                customer.msg(`${self.name()}阵亡无法售货`)
            }
        },
    },
    'W': {
        leader: '守卫者npc-1',
        name: '武士',
        desc: `1. 武士可以点击一个身边的玩家进行保护\n2. 不擅长打飞船但擅长近战, 对地面的敌人有较高的伤害\n3. 按住攻击键不放可以格挡敌人的攻击, 只受35%的伤害`,
        abv: '武',
        atkName: '近战',
        atk: (user) => (W_DMG_BUFF * user.atk) | 0,
        contrib: 1,
        rank: [
            '保镖武士',
            '资深保镖',
            '忠诚卫士',
            '终极卫士',
        ],
        /** @param e {GameInputEvent}*/
        action(e) {//shoot
            const target = e.raycast.hitEntity
            const self = e.entity
            const weaponId = self.data.wear.武器
            if (target && target.isPlayer) {
                if (e.raycast.distance > 15) {
                    self.msg(`离得太远, 无法保护`)
                }
                else if (target.data.role === self.data.role) {
                    self.msg(`同行不需要受到保护`)
                } else {
                    if (target._protector && target._protector.hp > 0 && !target._protector.destroyed && target._protector.position.distance(target.position) <= W_PROTECT_RADIUS) {
                        self.msg(`${target.name()}已被 ${target._protector.name()}保护`)
                    } else {
                        target._protector = self
                        let protectee
                        if (self._protectee) {
                            protectee = self._protectee.filter(az => az._protector == self)
                        } else {
                            protectee = [];
                        }
                        if (protectee && protectee.length >= Math.floor(self.lv() / 15) + 2) {
                            protectee[0].msg(`${protectee[0]._protector.name()}解除了对你的保护`)
                            delete protectee[0]._protector
                            delProtectIcon(protectee[0])
                        }
                        if (!self._protectee) {
                            self._protectee = [];
                        }
                        self._protectee.push(target)
                        self.msg(`${target.name()}在${W_PROTECT_RADIUS}格以内可以受到你保护`)
                        target.msg(`你在${self.name()}的${W_PROTECT_RADIUS}格范围内受到保护`)
                        addProtectIcon(target)
                    }
                }
            }
            else if (weaponId) {
                weaponEffect(self, target, e, weaponId)
            }
            else {
                self.msg(`需要装备武器才能攻击`)
            }
        },
        /** 
         * @param customer {GameEntity}
         * @param self {GameEntity}
         * */
        serve(customer, self) {
            customer.msg(`${Role[self.role].name} ${self.name()}暂无服务技能`)
        },
    },
    'N': {
        leader: 'jiqirennpc-1',
        name: '学生',
        desc: `作为学生, 守护和发展城市的责任还没落到你的身上, 但是你有强大的学习能力和为城市服务的潜力\n在学校修够${GRAD_CERT}\0学分毕业后才能出来找工作哦~`,
        abv: '学',
        atkName: '学习',
        atk: (user) => 90,
        contrib: 1,
        rank: ['小学生', '初中生', '高中生', '大学生'],
        /** @param e {GameInputEvent}*/
        action(e) {
            // e.entity.msg('在学校修够50\0学分取得毕业证, 然后找份工作吧~')
            const user = e.entity

            if (user.hasItem('grad')) {
                user.dlgMsg(`你准备去职业中心入职了, 去职业中心可以沿着地面黄色路线走, 在有"JOB"字样的楼群.\n但更快捷的路径是找学习机们帮你瞬移过去.`, '新手指引')
            } else {
                user.menu(`回学校考试, 修够50\0学分取得毕业证, 然后找份工作吧~`, '新手指引', {
                    瞬移回学校() {
                        user.teleToId('jiqirennpc-1')
                    }
                })
            }
        },
        /** 
         * @param customer {GameEntity}
         * @param self {GameEntity}
         * */
        serve(customer, self) {
            customer.msg(`${Role[self.role].name} ${self.name()}暂无服务技能`)
        },
    },
}
Role[''] = Role.N

function canUse(user, itemId) {
    const item = ItemDict[itemId]
    const owner = item.owner
    if (owner && user.role) {
        return owner.includes(user.role)
    } else {
        return true
    }
}

function canBuy(user, itemId) {
    return canUse(user, itemId) || user.role === 'T'
}

function useBy(itemId) {
    const item = ItemDict[itemId]
    const owner = item.owner
    if (owner) {
        return owner.split('').map(e => Role[e].name).join('/')
    } else {
        return ''
    }
}

function spawn(mesh, position) {
    // if (mesh === '1luxifa') return {}; // 路西法数据异常测试
    let e = world.createEntity({
        mesh: meshPath(mesh),
        meshScale: MeshScale,
        collides: false, // 开启碰撞
        gravity: false,
        friction: 1, // 关闭摩擦力
        mass: 0,
        position,
    })
    if (!e) return Phold
    e._mesh = mesh
    // e.addTag('')
    // e.enableDamage = true
    return e
}

function meshPath(name) {
    return `mesh/${name}.vb`
}

function calcSalary(user) {
    return Math.pow(user.contrib * Role[user.role].contrib, 0.9) | 0
}
global.calcSalary = calcSalary
function calcExpReward(user) {
    return Math.pow(user.contrib * Role[user.role].contrib, 0.82) | 0
}


function jobLeader(name, role) {
    const jobName = Role[role].name

    return {
        name,
        text(user) {
            if (user.role === role) {
                return `工作还顺利吗?`
            } else {
                return `这里是${Role[role].name}营房, 有什么可以帮到你?`
            }
        },
        options: {
            入职: {
                label: (u, n, k) => `使用毕业证入职, 成为${jobName}`,
                cond: (user) => user.hasItem('grad'),
                async run(user) {
                    if (user.role !== 'N') return
                    user.data.role = role

                    let text
                    if (role === 'S') {
                        text = user.gainItemListAndCoin('', ['gun1', 's1', 's1', 'h2', 'telc'], 1000)
                    }
                    else if (role === 'M') {
                        text = user.gainItemListAndCoin('', ['mbox', 'm1', 'm1', 'h1', 'r1'], 1000)
                    }
                    else if (role === 'E') {
                        text = user.gainItemListAndCoin('', ['tbox', 'e1', 'e1', 'beac', 'jPad'], 1000)
                    }
                    else if (role === 'T') {
                        text = user.gainItemListAndCoin('', ['bagT1', 'telc'], 2000)
                    }
                    else if (role === 'W') {
                        if (user.lv() < 5) {
                            await user.dlgMsg(`等级需要达到Lv5才能入职`)
                        }
                        text = user.gainItemListAndCoin('', ['sw1', 'm1', 'telc'], 1500)
                    }
                    user.data.item.removeElem('grad')
                    calcUserProp(user)
                    updatePlayer(user, 'role', 'item', 'coin')
                    await user.dlgMsg(`你提交了毕业证, 成功入职"${jobName}"\n获得以下入职礼包:\n` + text)
                },
            },
            结算: {
                label: (u, n, k) => `工资结算(${u.contrib | 0}贡献值)`,
                cond: (user) => user.role === role,
                async run(user, npc) {
                    const salary = calcSalary(user)
                    const jobScore = Math.pow(salary, 0.7)

                    const jobBonus = (jobScore * 1.2) | 0
                    const total = salary + jobBonus

                    let tt = ''
                    let exp = calcExpReward(user)
                    if (user.lv() >= 20) {
                        tt += '20级后补贴经验：' + Math.round(exp / 10)
                        exp *= 1.1
                        exp = Math.round(exp)
                    }

                    let text
                    let options
                    if (user.contrib >= CONTRIB_THRES) {
                        text = `你本轮目前累计${user.contrib | 0}贡献值\n\n工资: ${salary}\n${jobName}补贴: ${jobBonus}\n总计: +${total}${CURRENCY}\n\n经验: +${exp} ${tt}`
                        options = {
                            确认结算: {
                                async run(user, npc) {
                                    await takeReward(user, total, exp)
                                }
                            },
                        }
                    } else {
                        text = `你本轮累计贡献值还不到${CONTRIB_THRES}, 无法结算工资 :)`
                        options = {}
                    }
                    await npcDlg(user, npc, {
                        text,
                        options,
                    })
                },
            },
            如何转职: {
                label: (u, n, k) => `如何从"${u.roleName()}"转职成为"${jobName}"?`,
                cond: (user) => user.role !== 'N' && user.role !== role && !user.hasItem('trnsf'),
                async run(user, npc) {
                    const emptyItem = user.role === 'T' && role !== 'T'
                    await user.dlgMsg(`回学校修够${JOB_TRANSFER}\0学分找导师开转职介绍信,\n再把介绍信交给我就能完成转职手续.${emptyItem ? `\n另外, ${user.roleName()}需要把道具清理到${T_TRANSFER_ITEM}件或以下才能转职, 转职后你就不能拥有太多折扣商品了` : ''}`, npc.interactHint)
                },
            },
            转职: {
                label: (u, n, k) => `使用介绍信, 从"${u.roleName()}"转职到"${jobName}"`,
                cond: (user) => user.role !== 'N' && user.role !== role && user.hasItem('trnsf'),
                async run(user) {
                    const oldRole = user.roleName()
                    if (user.role === 'T' && user.data.item.length > T_TRANSFER_ITEM && !isPP(user) && !isAdmin(user)) {
                        return await user.dlgMsg(`转职时, ${oldRole}的库存不能超过${T_TRANSFER_ITEM}件`, '不符合转职条件')
                    }

                    for (id of Object.values(user.data.wear)) {
                        unequipNoUpdate(user, id)
                    }

                    user.data.role = role
                    user.data.item.removeElem('trnsf')
                    calcUserProp(user)
                    updatePlayer(user, 'role', 'item', 'wear')
                    await user.dlgMsg(`你成功的从"${oldRole}"转职到"${jobName}"`)
                },
            },
            攻略: {
                label: (u, n, k) => `${jobName}的职能`,
                async run(user, npc) {
                    await user.dlgMsg(Role[role].desc, npc.interactHint)
                },
            },
        }
    }
}

ItemEffect = {
    Fail: 1,
    Used: 2,
}

Deploy = {
    aa1(e, deployer, data) {
        Deploy.aa2(e, deployer, data)
    },
    aa3(e, deployer, data) {
        Deploy.aa2(e, deployer, data)
    },
    aa2(e, deployer, data) {
        e.onDie(async () => {
            e.pilot && e.pilot.msg(`${e.interactHint}被击毁, 操作员脱出`)
            e.leave()
            e.enableInteract = false
            e.glowRed()
            e.particle()
            await sleep(1000)
            e.destroy()
        })
        e.maxHp = e.hp = data.hp
        e.enableDamage = true
        e.interactRadius = 3
        e.interactHint = deployer.name() + `的${data.name}`
        e.enableInteract = true
        e.rotation = 0

        let target
        e.aim = (t) => {
            target = t
        }
        e.leave = () => {
            delete e.pilot
        }
        e.safeInteract(async ({ entity: user }) => {
            if (e.pilot) {
                user.msg(`${e.pilot.name()}正在操作这个炮台`)
                return
            }
            if (!(user.role === 'E' || user.role === 'S')) {
                user.msg(`${Role[user.role].name}不能操作炮台`)
                return
            }

            if (user._drone) {
                user._drone.leave()
            }

            user.msg(`进入自由索敌模式, 点击左键瞄准一个敌人, 右键离开炮台`)
            e.pilot = user
            e.isPrey = true
            user._turret = e

            user.enableInteract = false
            user.player.spectator = true
            user.player.canFly = true
            user.player.invisible = true
            user.player.enableInteract = false
            user.player.showName = false
            user.isPrey = false

            target = user

            let i = 0
            while (e.pilot) {
                e.rotZfaceTo(target, PI4, -PI2)
                if (i % data.firerate === 0 && target.isUfo && !target.hp <= 0) {
                    data.bullet(e, target, data)
                }
                await sleep(70)
                i++
            }

            delete user._turret
            delete e.pilot
            e.isPrey = false

            e.meshOrientation = Quat.rotateY(PI4)

            user.position.copy(e.position)

            user.player.spectator = false
            user.player.canFly = false
            user.player.invisible = false
            user.player.enableInteract = true
            user.player.showName = true
            user.isPrey = true
            user.enableInteract = true

            // user.mesh = e.mesh
            // user.meshOrientation = Quat.rotateY(PI2)
            // e.meshInvisible = true
            // user.meshScale.copy(e.meshScale)
            // user.position.copy(e.position)
        })
        e.addTag('turret')
    },
    dd1(e, deployer, data) {
        e.addTag('defense')
        e.maxHp = e.hp = data.hp
        e.collides = true;
        e.interactRadius = 2;
        e.interactHint = deployer.name() + `的${data.name}`
        e.enableInteract = true
        e.enableDamage = true
        e.meshScale = e.meshScale.scale(1.5);
        e.meshScale.y *= 1.2;
        e.onDie(() => {
            e.glowRed();
            e.particle();
            e.destroy();
        })
        e.isPrey = true;
        e.onTakeDamage(async ({ damage }) => {
            deployer.contrib += Math.ceil(damage / 2)
            await sleep(5000);
            if (e.hp > 0) {
                e.hp += Math.ceil(damage * 0.2);
            }
            if (e.hp > e.maxHp) {
                e.hp = e.maxHp;
            }
            await sleep(25000);
            if (e.hp > 0) {
                e.hp += Math.ceil(damage * 0.3);
            }
            if (e.hp > e.maxHp) {
                e.hp = e.maxHp;
            }

        })
    },
    dd2(e, deployer, data) {
        e.addTag('defense')
        e.maxHp = e.hp = data.hp
        e.collides = true;
        e.interactRadius = 2;
        e.interactHint = deployer.name() + `的${data.name}`
        e.enableInteract = true
        e.enableDamage = true
        e.meshScale = e.meshScale.scale(0.6);
        e.meshScale.y *= 1.3;
        let l = setInterval(() => {
            let t = e.nearestEntity(world.querySelectorAll('.eBullet,.eDrone').filter(e => !e.hasTag('eMissile') || Math.random() < 0.05));
            if (!t || t.position.distance(e.position) > 40) {

            } else {
                let dir = t.position.sub(e.position);
                dir = dir.scale(1 / dir.mag());
                e.meshOrientation = new GameQuaternion(0, 0, 0, 1).rotateY(Math.atan2(dir.z, dir.x) - Math.PI / 2);


                e.hurt(3);
                e.particleRate = 100;
                e.particleSize = 5;
                e.particleLifetime = 1;
                e.particleTarget = t;
                e.particleColor = new GameRGBColor(0, 0.1, 0.5);
                e.particleVelocity = dir.scale(50);

                setTimeout(() => { e.particleRate = 0 }, 200);
                if (t.hasTag('eBullet')) {
                    if (Math.random() < 0.15 * Math.max(15 - t.velocity.mag() * 2, 1)) {
                        t.destroy();
                    } else {
                        t.velocity.addEq(new GameVector3(random(-1, 1), random(0, 1), random(-1, 1)))
                        t.velocity = t.velocity.scale(0.6);
                    }
                    deployer.contrib += 2;

                }
                if (t.hasTag('eDrone')) {
                    t.hurt(Math.ceil(5 + (40 - t.position.distance(e.position)) / 2));
                    t._attacker = deployer;
                    deployer.contrib += 2;
                }
            }
        }, 500)
        e.onDie(() => {
            e.glowRed();
            e.particle();
            e.destroy();
            clearInterval(l)
        })
        e.isPrey = true;

    },
    beac(e, deployer, data) {
        const scale = 0.3
        e.meshScale.x *= scale
        e.meshScale.y *= scale
        e.meshScale.z *= scale
        e.collides = false
        e.interactRadius = 2
        e.safeInteract(async ({ entity: user }) => {
            user.msg(`可以让其他人使用"${ItemDict.telc.name}"传送过来`)
        })
        e.interactHint = deployer.name() + `的${DeviceNumber++}号${data.name}`
        e.enableInteract = true
        e.addTag('beacon')
        e.onDie(() => {
            e.destroy()
        })
    },
    med1(e, deployer, data) {
        e.maxHp = 400 + deployer.atk * 10
        e.hp = e.maxHp
        // const scale = 0.3
        // e.meshScale.x *= scale
        // e.meshScale.y *= scale
        // e.meshScale.z *= scale
        e.collides = false
        e.interactRadius = 2
        e.safeInteract(async ({ entity: user }) => {
            const dmg = user.maxHp - user.hp
            if (dmg > 0) {
                user.particleUp(p_green_color)
                user.hp += dmg
                e.hp -= dmg
                deployer.contrib += dmg * M_HEAL_CONTRIB * 0.5
            }
        })
        e.interactHint = deployer.name() + `的${data.name}`
        e.enableInteract = true
        e.enableDamage = true
        e.onDie(() => {
            e.destroy()
        })
    },
    city(e, deployer, data) {
        e.enableDamage = true
        e.addTag('turret')
        e.hp = e.maxHp = 300
        const scale = 0.9
        e.meshScale.x *= scale
        e.meshScale.y *= scale
        e.meshScale.z *= scale
        // e.collides = false
        e.interactRadius = 3
        const dict = {}

        let active = false
        e.safeInteract(async ({ entity: user }) => {
            if (active) return
            if (isBattling) {
                return user.msg(`城市交战中无法启动这个系统`)
            } else if (user.role === 'N') {
                return user.msg(`"${user.roleName()}"无法启动这个系统`)
            }
            const rep = dict[user.roleName()]
            if (rep) {
            } else {
                dict[user.roleName()] = user
            }
            const list = Object.values(dict)
            let text = list.map(u => `${u.name()}已代表"${u.roleName()}"确认系统启动`).join('\n')
            if (list.length >= 5) {
                active = true
                text += `\n\n成功启动城市修复`
                world.say(`${list.map(e => e.name())}成功启动城市修复协议`)
                e.glowGreen()
                e.particleUp(p_green_color)
                e.deployer.contrib += FIX_CITY_CONTRIB
                for (const p of list) {
                    p.contrib += ACK_FIX_CONTRIB
                }
                await sleep(100)
                repairCity()
                await sleep(1000)
                e.destroy()
            } else {
                text += `\n\n还差${5 - list.length}个职业代表才能启动`
            }
            return user.dlgMsg(text, data.name)
        })
        e.interactHint = deployer.name() + `的${data.name}`
        e.enableInteract = true
        e.onDie(() => {
            e.destroy()
        })
    },
    /** @param e {GameEntity}*/
    jPad(e, deployer, data) {
        e.meshScale.y *= 0.25
        e.meshScale.x *= 0.5
        e.meshScale.z *= 0.5
        e.interactRadius = 2
        e.interactHint = deployer.name() + `的${data.name}`
        e.enableInteract = true
        e.safeInteract(async ({ entity: user }) => {
            user.msg(`跳上来, 弹飞出去~`)
        })
        e.onEntityContact(({ other: user, axis }) => {
            if (user.isPlayer && axis.y === -1) {
                user.velocity.y = 2.5
            }
        })
        e.onDie(() => {
            e.destroy()
        })
    },
    d1(e, deployer, data) {
        e.fixed = false
        e.position.y += 2
        e.gravity = true

        e.leave = () => {
            if (e.pilot) {
                delete e.pilot._drone
                delete e.pilot
            }
        }
        e.explode = () => {
            const target = e.nearestEntity(world.querySelectorAll('.ship'))
            if (target) {
                e.target = target
                e.collides = false
                e.glowRed()
            } else {
                e.pilot.msg('目前探测不到敌方飞船')
            }
        }
        e.take = (user) => {
            if (e.hp <= 0) {
                user.msg('无人机已毁坏')
            }
            else if (e.target) {
                user.msg('无人机处于自爆模式')
            }
            else if (!e.pilot) {
                if (user.role === 'N') {
                    user.msg(`${user.roleName()}无法操作无人机`)
                }
                if (user._drone) {
                    user._drone.leave()
                }
                e.gravity = false
                e.pilot = user
                user._drone = e
                droneFollow(user, e)
            }
            else if (user === e.pilot) {
                user.menu('进行哪个操作?', e.interactHint, {
                    async 待机() {
                        e.leave()
                    },
                    async 索敌自爆() {
                        e.explode()
                    },
                })
            } else {
                user.msg(`无人机已被 ${e.pilot.name()}接管`)
            }

        }
        e.safeInteract(async ({ entity: user }) => {
            e.take(user)
        })
        e.onEntityContact(({ other }) => {
            if (other.isEnemy && e.pilot) {
                turretDamageToEnemy(e.pilot, e.deployer, other, 6)
                e.hurt(100)
            }
        })
        // e.onVoxelContact(()=>{
        // })
        e.onDie(async () => {
            e.leave()
            e.particle()
            e.glowRed()
            await sleep(1000)
            e.destroy()
        })
        e.onDestroy(() => {
            e.leave()
        })
        e.interactHint = deployer.name() + `的${data.name}`
        e.enableInteract = true
        e.interactRadius = 2.5
        e.addTag('turret')
    },
}

async function adjustY(e, bottomY) {
    await sleep(120)
    e.position.y = bottomY + e.bounds.y
    // console.log('ss')
}

// const Toolbox = ['tbox','tbox2','tboxa',tboxD]
function isWearingToolbox(user) {
    const weapId = user.data.wear.武器
    if (weapId) {
        return weapId.startsWith('tbox')
    } else {
        return false
    }
}

// const Medbox = ['mbox','mbox2','mboxa']
function isWearingMedbox(user) {
    const weapId = user.data.wear.武器
    return weapId.startsWith('mbox')
}
let boss1CallTime = 0
const Usage = {
    equip: {
        desc(user) {
            const desc = this.desc ? this.desc + '\n\n' : ''
            return desc + Object.entries(this.buff)
                .map(([k, v]) =>
                    `${PlayerProp[k]}: ${v > 0 ? '+' : ''}${(v > -1 && v < 1) ? `${v * 100}%` : v}`
                ).join('\n')
        },
        use(target, user) {
            const wear = user.data.wear
            if (this.type === '背包' && user.data.item.length > CAPACITY && !isAdmin(user) && !isPP(user)) {
                return [ItemEffect.Fail, `道具不多于${CAPACITY}件才能换下背包`]
            }
            if (wear[this.type] === this.id) {
                unequip(user, this.id)
                return [ItemEffect.Used, `卸下了"${this.name}"`]
            } else {
                wearEquip(user, this.id)
                return [ItemEffect.Used, `装备了"${this.name}"`]
            }
        }
    },
    refillAmmo: {
        desc(user) {
            return `重新装满"${ItemDict[this.target.split(' ')[0]].name}"`
        },
        use(target, user) {
            const weaponId = user.data.wear.武器
            const tgName = ItemDict[this.target.split(' ')[0]].name
            if (!weaponId || !this.target.includes(weaponId)) return [ItemEffect.Fail, `需要装备"${tgName}"才能使用"${this.name}"`]
            user.ammo = 100

            if (ItemDict[this.id].ammo != undefined) {
                user.ammo = parseInt(ItemDict[this.id].ammo);
            }
            return [ItemEffect.Used, `${tgName}重新装满${user.ammo}%`]
        },
    },
    deploy: {
        desc(user) {
            return this.desc
        },
        use(target, user) {
            let pos = user.position
            pos = [pos.x | 0, (pos.y - user.bounds.y - 1) | 0, pos.z | 0]
            // console.log(pos)
            let reason
            if (this.needTool && !isWearingToolbox(user)) {
                return [ItemEffect.Fail, `"${this.name}"需要装备工具箱才能部署`]
            }
            if (!voxels.getVoxelId(...pos)) {
                return [ItemEffect.Fail, `需要在平地上部署`]
            }
            if (MAP[`${pos[0]},${pos[1] + 1},${pos[2]}`] || MAP[`${pos[0]},${pos[1] + 2},${pos[2]}`]) {
                return [ItemEffect.Fail, `不能在弹坑里部署`]
            }
            pos[0] += 0.5
            pos[2] += 0.5

            if (Deployments.length >= DeployQuota) {
                return [ItemEffect.Fail, `地图总共只允许部署${DeployQuota}个设施, 让小伙伴们拆除掉一些吧~`]
            } else if (Deployments.filter((e) => e.deployer.player.userKey === user.player.userKey).length >= DeployIndiQuota) {
                return [ItemEffect.Fail, `每人最多允许部署${DeployIndiQuota}个设施`]
            }

            if (this.uniq) {
                if (world.querySelector('#' + this.uniq)) {
                    return [ItemEffect.Fail, `"${this.name}"只能部署一台, 城市中已存在另一台`]
                }
            }
            if (this.prevent) {
                const prevent_msg = this.prevent(user, this)
                if (prevent_msg) {
                    return [ItemEffect.Fail, prevent_msg]
                }
            }
            const e = spawn(this.mesh, pos)
            if (this.uniq) {
                e.id = this.uniq
            }
            e.noRepair = this.noRepair
            Deployments.push(e)
            e.addTag('Deployment');
            e.enableDamage = true
            e.deployer = user
            e.fixed = true
            e.collides = true
            e.meshEmissive = 0.015
            adjustY(e, user.position.y - user.bounds.y)
            const dep = Deploy[this.id]
            if (dep) {
                dep(e, user, this)
                const contrib = this.price * E_DEPLOY_CONTRIB
                user.contrib += contrib
                user.roundContrib += contrib
                user._deployAcc += this.price
            }
            e.onDestroy(() => {
                Deployments.removeElem(e)
            })
            return [ItemEffect.Used, `部署了${this.name}`]
        },
    },
    heal: {
        desc(user) {
            return `在找不到其他医生的情况下, 给自己恢复${this.hp}点生命的低效医疗包
            (医生或武士使用有双倍疗效)`
        },
        use(target, user) {
            const hp = (this.hp * jitter() * ('MW'.includes(user.role) ? M_ITEM_BUFF * atkRatio(user) : 1)) | 0
            user.hp += hp
            if (user.hp > user.maxHp) {
                user.hp = user.maxHp
            }
            return [ItemEffect.Used, `${user.name()}恢复了${hp}点生命\n当前生命(${user.hp}/${user.maxHp})`]
        },
    },
    disarm: {
        desc(user) {
            return `启动远程设施管理协议, 拆除无人维护的设施
            (可重复使用)`
        },
        use(target, user) {
            tryDisarm(user, target)
            return [ItemEffect.Used, `启动协议, 拆除${target.interactHint}`]
        },
    },
    addCoin: {
        use(target, user) {
            user.gainCoin(this.coin)
            return [ItemEffect.Used, `你获得${this.coin}${CURRENCY}`]
        },
        desc(user) {
            return `玩家增加${this.coin}${CURRENCY}`
        },

    },
    addExp: {
        use(target, user) {
            const text = user.gainExp(this.exp)
            return [ItemEffect.Used, text]
        },
        desc() {
            return `增加${this.exp}经验`
        },
    },
    makeItem: {
        use(item, user) {
            if (!isAdmin(user)) return [ItemEffect.Fail, '使用无效, 管理员才能使用']
            makeGift(user, item.id)
            return [ItemEffect.Stay, `制作道具"${item.name}"`]
        }
    },
    teleportUser: {
        use(target, user) {
            // if (!isAdmin(user)) return [ItemEffect.Fail, '使用无效, 管理员才能使用']
            user.position.copy(target.position)
            user.position.y += 1 + Math.random()
            user.particleUp(p_yellow_color)
            return [ItemEffect.Used, `传送到${target.interactHint}`]
        },
        desc() {
            return `瞬移到指定的量子信标, 使用后会因过热而损毁`
        },
    },
    boss1Call: {
        use(target, user) {
            if (bigBossBattle == false && new Date().getTime() - boss1CallTime >= 60 * 60 * 1000 && activePlayers().length > 20 && isBattling == false) {
                logger.log(`[Info] <${user.player.userId}>${user.player.name} Spawned Lucifa`);
                bigBossBattle = true;
                boss1CallTime = new Date().getTime();
                for (const e of world.querySelectorAll('#路西法叛徒-1')) {
                    e.meshInvisible = false;
                    e.enableInteract = true;
                };
                world.say(user.name() + '召唤了路西法！！');
                return [ItemEffect.Used, `${user.name()}成功召唤路西法`]
            } else {
                return [ItemEffect.Fail, `路西法进行中或路西法召唤冷却没到或人数不大于20或城市交战中`]

            }
        },
        desc() {
            return `召唤路西法`
        }
    },
    mute: {
        /** @param user {GameEntity}*/
        use(entity, user) {
            if (!isAdmin(user)) return [ItemEffect.Fail, '使用无效, 管理员才能使用']
            user.player.muted = !user.player.muted
            return [ItemEffect.Stay, `禁言模式${user.player.muted}`]
        }
    },
}

function ripEquip(user) {
    if (user.data.coin > 3000) {
        let rrr = 0.1;
        if (user.data.coin > 30000) {
            rrr = 0.15;
        }
        if (Math.random() < rrr) {
            const itemId = Object.values(user.data.wear).pickOne()
            if (itemId) {
                if (itemId[itemId.length - 1] == 'a' && Math.random() < 0.6) {
                    return;
                }

                const item = ItemDict[itemId]
                if (!item.noBreak) {
                    user.discardItem(itemId)
                    user.msg(`你装备的"${item.name}"严重损毁`)
                }
            }
        }
    }
}
const ItemList = [
    { id: 'telc', price: 100, name: '瞬移芯片', type: '瞬移', list: 'beacon', use: Usage.teleportUser, desc: `利用瞬移芯片把人传送到信标的小型装置, 即用即扔的消耗品` },
    { id: 'boss1Call', rare: true, name: '路西法召唤卡', type: '召唤卡', list: 'self', use: Usage.boss1Call, desc: `消耗使用路西法召唤` },
    //{ id: 'boss1Calla', rare: true, name: '路西法召唤卡碎片', type: '召唤卡', list: 'self', use: Usage.luxifaCall, desc: `消耗使用路西法召唤` },

    { id: 's2', price: 100, name: '火箭筒弹药', target: 'bzk1 bzk1a', type: '弹药', list: 'self', use: Usage.refillAmmo, owner: 'S' },
    { id: 's3', price: 100, name: '高斯枪弹药', target: 'gun2', type: '弹药', list: 'self', use: Usage.refillAmmo, owner: 'S' },
    { id: 's4', price: 500, name: '机枪能量弹夹', target: 'gunG gun3 gun4', type: '弹药', list: 'self', use: Usage.refillAmmo, owner: 'S', ammo: 500, },

    { id: 'm1', price: 100, name: '医疗器件', target: 'mbox mbox2 mboxa mboxA', type: '弹药', list: 'self', use: Usage.refillAmmo, owner: 'M' },
    { id: 'e1', price: 100, name: '工具零件', target: 'tbox tbox2 tboxa tboxD tboxd', type: '弹药', list: 'self', use: Usage.refillAmmo, owner: 'E' },
    { id: 'e2', price: 550, name: '工具零件自动填充夹', target: 'tbox tbox2 tboxa tboxD tboxd', type: '弹药', list: 'self', use: Usage.refillAmmo, owner: 'E', ammo: 500 },


    { id: 'b1', price: 100, name: '能量电池', target: 'sw1 sw1a swH1 shH1 sw1H', type: '弹药', list: 'self', use: Usage.refillAmmo, owner: 'W' },
    { id: 'b2', price: 1100, name: '巨型能量电池', target: 'sw1 sw1a swH1 shH1 sw1H', type: '弹药', list: 'self', use: Usage.refillAmmo, owner: 'W', ammo: 1000 },

    { id: 's1', price: 100, name: '防卫枪弹药', target: 'gun1', type: '弹药', list: 'self', use: Usage.refillAmmo },
    { id: 'r1', price: 300, name: '复活装置', type: '复活', list: 'self', use: `辅助"急救箱"实施复活手术的小型装置`, owner: 'M' },
    { id: 'h1', price: 300, name: '自助医疗包Lv1', type: '生命', hp: 30, list: 'self', use: Usage.heal },
    { id: 'h2', price: 500, name: '自助医疗包Lv2', type: '生命', hp: 50, list: 'self', use: Usage.heal },
    { id: 'dArm', infinite: true, price: 800, name: '设施拆除协议终端', type: '拆除', list: 'deploy', use: Usage.disarm, owner: 'E' },

    { id: 'd1', price: 250, name: '战术无人机', type: '设施', mesh: '无人机', list: 'self', use: Usage.deploy, desc: `有多种用途的军用无人机`, owner: 'ES' },
    { id: 'aa1', hp: 400, needTool: true, price: 1200, mesh: '防空机枪', name: '自动雷射炮台', type: '设施', list: 'self', use: Usage.deploy, firerate: 6, desc: `自动雷射防空炮, 射程不限, 弹药不限`, prevent: (e) => e.position.y < 21 && '炮台最好部署在更高的地方, 避免弹道遮挡', owner: 'E', atk: 1, bullet: (shooter, target, data) => gunShoot(shooter, target, 'beam0', 8, 'pBullet', data) },
    { id: 'aa2', hp: 600, needTool: true, price: 2200, mesh: '导弹', name: '自动导弹台', type: '设施', list: 'self', use: Usage.deploy, firerate: 8, desc: `自动防空导弹, 装甲厚重, 射程不限, 弹药不限`, prevent: (e) => e.position.y < 21 && '炮台最好部署在更高的地方, 避免弹道遮挡', owner: 'E', atk: 2, bullet: (shooter, target, data) => missile(shooter, target, '大号的导弹', 1, 'pBullet', data) },
    { id: 'aa3', hp: 500, needTool: true, price: 2300, mesh: '聚能炮2', name: '聚能加农炮台', type: '设施', list: 'self', use: Usage.deploy, firerate: 11, desc: `自动瞄准, 射程不限, 弹药不限`, prevent: (e) => e.position.y < 21 && '炮台最好部署在更高的地方, 避免弹道遮挡', owner: 'E', atk: 3, bullet: (shooter, target, data) => gunShoot(shooter, target, '蓝光球', 8, 'pBullet', data) },
    { id: 'dd1', hp: 1000, needTool: false, price: 1800, mesh: '保护罩', name: '聚能防护罩', type: '设施', list: 'self', use: Usage.deploy, desc: `由工程师的科技之力和武士的守护之力共同凝聚而成的防护罩，额外拥有受伤5秒后恢复伤害20%效果，受伤30秒后再恢复30%`, owner: 'EW', },
    { id: 'dd2', hp: 400, needTool: false, price: 1300, mesh: '移动炮台', name: '电磁拦截器', type: '设施', list: 'self', use: Usage.deploy, desc: `士兵和工程师可用，使用电磁脉冲自动拦截部分子弹，还可以自动攻击附近无人机和小怪`, owner: 'SE', },


    { id: 'med1', noRepair: true, price: 1300, name: '公共治疗装置', type: '设施', mesh: '治疗装置', list: 'self', use: Usage.deploy, desc: `部署之后, 任何人都能用它治疗自己`, owner: 'M' },
    { id: 'beac', price: 200, needTool: true, name: '量子信标', type: '设施', mesh: '按钮-2', list: 'self', use: Usage.deploy, desc: `用途广泛的定位装置`, owner: 'E' },
    { id: 'jPad', price: 150, needTool: true, name: '弹射跳台', type: '设施', mesh: '按钮-0', list: 'self', use: Usage.deploy, desc: `踩上去就会往上弹射, 轻松跳到屋顶`, owner: 'E' },
    { id: 'city', price: 3000, uniq: 'fixall', needTool: true, name: '城市还原共识系统', type: '设施', mesh: 'fixall', list: 'self', use: Usage.deploy, desc: `士兵/工程师/医生/商人 各派一名代表确认启动城市还原协议`, owner: 'E' },
    // { id: 'fw1', price: 400, name: '烟花:春节十二闪', type: '玩具', list: 'self', use: Usage.deploy, desc: `违禁品, 市区禁止燃放. 奸商号称它会闪12下, 但实际只闪两下...` },

    { id: 'grad', rare: true, price: 0, name: '毕业证', type: '证书', list: 'self', use: '在就业中心入职时所需要的证书, 学习机能把你瞬移到就业中心' },
    { id: 'trnsf', rare: true, price: 0, name: '转职介绍信', type: '证书', list: 'self', use: '拿着这个介绍信回就业中心的营房找相应的职业负责人进行转职, 职业中心沿着地面黄色路线走, 在"JOB"字样的楼群' },

    { id: 'bandR', price: 0, rare: true, name: '铁心臂章', type: '臂章', use: Usage.equip, desc: `${FactionR}成员的标志`, list: 'self', buff: { faction: FactionR, atk: 3 }, mesh: '臂章铁心' },
    { id: 'bandN', price: 0, rare: true, noBreak: true, name: '超时空大佬臂章', type: '臂章', use: Usage.equip, desc: `超时空大佬的象征，只有在“大佬云集”活动中才可以获得`, list: 'self', buff: { atk: 8 }, mesh: '大佬臂章' },
    { id: 'bandG', price: 0, rare: true, name: '护联臂章', type: '臂章', use: Usage.equip, desc: `${FactionG}成员的标志`, list: 'self', buff: { faction: FactionG, def: 5 }, mesh: '臂章护联' },
    { id: 'hatS1', price: 580, name: '防弹头盔', type: '帽子', use: Usage.equip, desc: `战斗中有效保护头部的头盔`, list: 'self', owner: 'S', buff: { def: 4 }, mesh: '士兵帽' },
    { id: 'hatS1a', price: 2580, rare: true, name: '异星防弹头盔', type: '帽子', use: Usage.equip, desc: `异星科技改造出来的防弹头盔`, list: 'self', owner: 'S', buff: { def: 8 }, mesh: '牛角士兵帽' },
    { id: 'suitS1', owner: 'S', price: 700, name: '防弹马甲', type: '衣服', use: Usage.equip, desc: `战斗中有效保护身体的衣服, 具备防火特性`, list: 'self', buff: { def: 4 }, mesh: '士兵衣服' },
    { id: 'hatG', owner: 'S', name: '黄金审判者头盔', rare: true, noBreak: true, type: '帽子', use: Usage.equip, desc: `由黄金打造的头盔，拥有无与伦比的防御，无限耐久`, list: 'self', buff: { def: 12 }, mesh: '黄金帽' },
    { id: 'suitG', owner: 'S', name: '黄金审判者马甲', rare: true, noBreak: true, type: '衣服', use: Usage.equip, desc: `由黄金打造的防弹马甲，拥有极高的防御，可抗火，无限耐久`, list: 'self', buff: { def: 12 }, mesh: '黄金衣服' },
    { id: 'hatE1', price: 590, name: '工程帽', type: '帽子', use: Usage.equip, desc: `保护施工人员不受致命伤害的头盔`, list: 'self', owner: 'E', buff: { def: 2 }, mesh: '工人帽子' },
    { id: 'hatE1a', price: 2590, rare: true, name: '异星工程帽', type: '帽子', use: Usage.equip, desc: `异星科技改造出来的工程帽`, list: 'self', owner: 'E', buff: { def: 5 }, mesh: '牛角工人帽' },

    { id: 'hatD1', noBreak: true, rare: true, name: '紫电工程帽', type: '帽子', use: Usage.equip, desc: `保护施工人员不受致命伤害的头盔，降低轰炸弹伤害75%`, list: 'self', owner: 'E', buff: { def: 5 }, mesh: '雷电工人帽子' },
    { id: 'suitE1', owner: 'E', price: 610, name: '工程服', type: '衣服', use: Usage.equip, desc: `保护施工人员的衣服, 具备防火特性`, list: 'self', buff: { def: 5 }, mesh: '工人衣服' },

    { id: 'suitD1', owner: 'E', noBreak: true, rare: true, name: '紫电工程服', type: '衣服', use: Usage.equip, desc: `保护施工人员的衣服, 具备防火特性, 降低虫子伤害75%`, list: 'self', buff: { def: 8 }, mesh: '雷电工人衣服' },

    { id: 'hatM1', price: 600, name: '医生帽', type: '帽子', use: Usage.equip, desc: `防止细菌病毒依附在头发的帽子`, list: 'self', owner: 'M', buff: { def: 2 }, mesh: '医生帽' },
    { id: 'hatM1a', price: 2600, rare: true, name: '异星医生帽', type: '帽子', use: Usage.equip, desc: `异星科技改造出来的的医生帽子`, list: 'self', owner: 'M', buff: { def: 5 }, mesh: '牛角冠帽' },
    { id: 'hatA', noBreak: true, rare: true, name: '皇家医生帽', type: '帽子', use: Usage.equip, desc: `皇家医生御用抗菌帽`, list: 'self', owner: 'M', buff: { def: 10 }, mesh: '皇家医生帽' },

    { id: 'suitM1', owner: 'M', price: 610, name: '医生服', type: '衣服', use: Usage.equip, desc: `保护医务人员的衣服, 具备防火特性`, list: 'self', buff: { def: 4 }, mesh: '医生服' },
    { id: 'suitA', owner: 'M', noBreak: true, rare: true, name: '皇家医生服', type: '衣服', use: Usage.equip, desc: `皇家医生御用防菌服, 具备防火特性`, list: 'self', buff: { def: 10 }, mesh: '皇家医生服' },

    { id: 'hatT1', owner: 'T', price: 580, name: '商人帽', type: '帽子', use: Usage.equip, desc: `皮实耐用的帽子`, list: 'self', buff: { def: 2 }, mesh: '商人帽' },
    { id: 'hatS', owner: 'T', noBreak: true, rare: true, name: '海洋商人帽', type: '帽子', use: Usage.equip, desc: `海界商人专用帽子`, list: 'self', buff: { def: 6, atk: 3, }, mesh: '海洋商人帽' },

    { id: 'hatT1a', owner: 'T', price: 1580, rare: true, name: '异星商人帽', type: '帽子', use: Usage.equip, desc: `异星科技改造出来的的商人帽`, list: 'self', buff: { def: 5 }, mesh: '牛角商人帽' },
    { id: 'suitT1', owner: 'T', price: 720, name: '商人服', type: '衣服', use: Usage.equip, desc: `适应送货途中严酷环境的衣服, 具备防火特性`, list: 'self', buff: { def: 6 }, mesh: '商人衣服' },
    { id: 'suitS', owner: 'T', noBreak: true, rare: true, name: '海洋商人服', type: '衣服', use: Usage.equip, desc: `海界商人最爱的商人服`, list: 'self', buff: { def: 8, atk: 2, }, mesh: '海洋商人服' },
    { id: 'bagS', noBreak: true, rare: true, name: '海洋商人背包', type: '背包', use: Usage.equip, desc: `海界商人最爱的亡命级背包`, list: 'self', owner: 'T', buff: { capacity: 60, title: '海界亡命商人', def: -15, atk: 10 }, mesh: '海洋商人背包' },
    { id: 'rodS', noBreak: true, rare: true, name: '海商权杖', type: '武器', use: Usage.equip, desc: `象征海界商人地位的手杖`, list: 'self', owner: 'T', buff: { atk: 5 }, mesh: '异界权杖' },
    { id: 'rodX', noBreak: true, rare: true, name: '星月权杖', type: '武器', use: Usage.equip, desc: `太空友人赠与超时空商人的权杖`, list: 'self', owner: 'T', buff: { atk: 3 }, mesh: '星月权杖' },

    { id: 'hatH1', owner: 'W', noBreak: true, rare: true, name: '赤焰战盔', type: '帽子', use: Usage.equip, desc: `熔岩之地的产物，可以加强使用者的能力。`, list: 'self', buff: { def: 10, atk: 5 }, mesh: '火焰头盔' },
    { id: 'suitH1', owner: 'W', noBreak: true, rare: true, name: '赤焰铠甲', type: '衣服', use: Usage.equip, desc: `熔岩之地的产物，可以加强使用者的能力。`, list: 'self', buff: { def: 10, atk: 4 }, mesh: '火焰盔甲' },
    { id: 'hatHB', owner: 'W', noBreak: true, rare: true, name: '残响战盔', type: '帽子', use: Usage.equip, desc: `致战地冲突的勇者武士`, list: 'self', buff: { def: 10, atk: 5 }, mesh: '冰晶头' },
    { id: 'suitHB', owner: 'W', noBreak: true, rare: true, name: '残响铠甲', type: '衣服', use: Usage.equip, desc: `致战地冲突的勇者武士`, list: 'self', buff: { def: 10, atk: 4 }, mesh: '冰晶甲' },
    { id: 'hatHD', owner: 'W', noBreak: true, rare: true, name: '赤焰战盔·龙魂', type: '帽子', use: Usage.equip, desc: `经过了赤焰龙神的强化，属性得到了极大的提升。`, list: 'self', buff: { def: 15, atk: 10 }, mesh: '烈火头盔' },
    { id: 'suitHD', owner: 'W', noBreak: true, rare: true, name: '赤焰铠甲·龙魂', type: '衣服', use: Usage.equip, desc: `经过了赤焰龙神的强化，属性得到了极大的提升。`, list: 'self', buff: { def: 15, atk: 8 }, mesh: '烈火铠甲' },
    { id: 'swH1', noBreak: true, rare: true, name: '赤焰之刃', ammoId: 'b1', spend: 2, type: '武器', use: Usage.equip, desc: `熔岩之地的产物，可以加强使用者的能力。（左剑发射火刃并且向前突刺）`, list: 'self', buff: { radius: 12, atk: 20 }, mesh: '火龙刀', delay: 300, owner: 'W' },
    { id: 'shH1', noBreak: true, rare: true, name: '赤焰之盾', ammoId: 'b1', type: '盾', use: Usage.equip, desc: `熔岩之地的产物，可以加强使用者的能力。（长按下顿1秒有力场振飞小怪和炮弹，消耗3电池）`, list: 'self', buff: { def: 6, atk: 3, }, mesh: '火焰盾牌', owner: 'W' },
    { id: 'hatZ', owner: 'S', name: '追猎者头盔', rare: true, noBreak: true, type: '帽子', use: Usage.equip, desc: `太空牛仔在执行任务的时候不慎遗落了这顶帽子，他发起了宇宙悬赏，找到这顶帽子的会赠与他纯金牛仔胸章`, list: 'self', buff: { def: 7 }, mesh: '追猎头盔' },
    { id: 'suitZ', owner: 'S', name: '追猎者马甲', rare: true, noBreak: true, type: '衣服', use: Usage.equip, desc: `太空号飞船为太空牛仔研发的防弹衣，天下最厉害的内鬼也没办法破开！`, list: 'self', buff: { def: 8 }, mesh: '追猎护甲' },

    { id: 'hatW1', owner: 'W', price: 780, name: '智能战盔', type: '帽子', use: Usage.equip, desc: `装载了智能格斗芯片的近战头盔`, list: 'self', buff: { def: 5, atk: 2 }, mesh: '守卫者头盔' },
    { id: 'hatW1a', owner: 'W', price: 1780, rare: true, name: '异星智能战盔', type: '帽子', use: Usage.equip, desc: `异星科技改造出来的近战头盔`, list: 'self', buff: { def: 8, atk: 4 }, mesh: '牛角头盔' },
    { id: 'suitW1', owner: 'W', price: 920, name: '智能铠甲', type: '衣服', use: Usage.equip, desc: `装载了智能格斗芯片的近战铠甲`, list: 'self', buff: { def: 10, atk: 1 }, mesh: '守卫者盔甲' },
    { id: 'bagT1', noBreak: true, price: 1000, name: '物流背包Lv1', type: '背包', use: Usage.equip, desc: `大幅增加道具携带量的入门级配送员背包`, list: 'self', owner: 'T', buff: { capacity: 45 }, mesh: '商人背包' },
    { id: 'bagT2', noBreak: true, price: 1000, name: '物流背包Lv2', type: '背包', use: Usage.equip, desc: `大幅增加道具携带量的进阶配送员背包`, list: 'self', owner: 'T', buff: { capacity: 50 }, mesh: '商人背包' },
    { id: 'bag++', noBreak: true, rare: true, name: '战术背包lv1', type: '背包', use: Usage.equip, desc: `重量较轻的战术背包，适合全部人使用`, list: 'self', buff: { capacity: 10 }, mesh: '扩容背包lv1' },
    { id: 'bagC', noBreak: true, rare: true, name: '创造者背包', type: '背包', use: Usage.equip, desc: `致为超时空发展做出贡献的人`, list: 'self', buff: { capacity: 25 }, mesh: '创造者背包' },
    { id: 'bagFly', noBreak: true, price: 6000, name: '喷气背包', type: '背包', use: Usage.equip, desc: `喷气背包～二段跳后获得升力`, buff: {}, list: 'self', mesh: '喷气背包' },
    { id: 'bagTX', rare: true, noBreak: true, price: 35000, name: '限量版物流背包', type: '背包', use: Usage.equip, desc: `测试版中元老级商人能买到的限量版配送员背包`, list: 'self', owner: 'T', buff: { capacity: 60, title: '测试版王牌销售' }, mesh: '商人背包' },
    { id: 'bagTD', noBreak: true, price: 35000, name: '高风险物流背包', type: '背包', use: Usage.equip, desc: `牺牲人身安全换取优惠力的入门级亡命销售员背包`, list: 'self', owner: 'T', buff: { capacity: 55, title: '富贵险中求', def: -10, atk: 4 }, mesh: '商人背包' },
    { id: 'roda', price: 2800, rare: true, name: '异星甲虫手杖', type: '武器', use: Usage.equip, desc: `异星商人的身份象征`, list: 'self', buff: { atk: 4 }, mesh: '甲虫手杖', owner: 'T' },
    { id: 'sw1', price: 600, name: '护卫剑', ammoId: 'b1', spend: 1, type: '武器', use: Usage.equip, desc: `依靠能量驱动的近战武器, 剑刃带有分子高频振动`, list: 'self', buff: { radius: 10, atk: 20 }, mesh: '守护剑', delay: 150, owner: 'W' },
    { id: 'sw1a', price: 2600, rare: true, name: '异星战锤', ammoId: 'b1', spend: 1, type: '武器', use: Usage.equip, desc: `异星使者赠与最强武士的近战武器`, list: 'self', buff: { radius: 12, atk: 30 }, mesh: '牛角锤', delay: 150, owner: 'W' },
    { id: 'sw1H', price: 2600, rare: true, name: '烈焰战锤', ammoId: 'b1', spend: 1, type: '武器', use: Usage.equip, desc: `熔岩之地的产物，可以加强使用者的能力。（提升本能）`, list: 'self', buff: { radius: 12, atk: 40 }, mesh: '火焰锤', delay: 150, owner: 'W' },
    { id: 'sh1', price: 600, name: '护卫盾', type: '盾', use: Usage.equip, desc: `非常坚固的纳米材料盾牌`, list: 'self', buff: { def: 3 }, mesh: '守卫盾牌', owner: 'W' },
    { id: 'sh2', price: 600, name: '单兵战术盾', type: '盾', use: Usage.equip, desc: `士兵专属的单兵战术盾牌`, list: 'self', buff: { def: 3 }, mesh: '单兵用盾牌', owner: 'S' }, { id: 'sh2', price: 600, name: '单兵战术盾', type: '盾', use: Usage.equip, desc: `士兵专属的单兵战术盾牌`, list: 'self', buff: { def: 3 }, mesh: '单兵用盾牌', owner: 'S' },
    { id: 'sh3', price: 600, rare: true, name: '残响盾牌', type: '盾', use: Usage.equip, desc: `残响星科技改造后的盾牌，拥有更高的防护能力`, list: 'self', buff: { def: 15 }, mesh: '残响盾', owner: 'S' },
    { id: 'gun1', price: 400, name: '防卫枪', ammoId: 's1', spend: 1, type: '武器', use: Usage.equip, desc: `所有人都懂得使用的入门级枪械`, list: 'self', buff: { radius: 50, atk: 2 }, mesh: '防卫枪', delay: 250 },
    // { id: 'gun11', price: 500, name: '防卫枪(左手)', ammoId: 's1', spend: 1, type: '武器2', use: Usage.equip, desc: `受过训练的左手才能灵活使用的防卫枪`, list: 'self', buff: { radius: 15, atk: 1 }, mesh: '枪', delay: 250, owner:'S' },
    { id: 'gun2', owner: 'S', price: 1100, ammoId: 's3', spend: 2, name: '高斯来复枪', type: '武器', use: Usage.equip, desc: `处于实验阶段的小型磁脉冲枪械`, list: 'self', buff: { radius: 85, atk: 3 }, mesh: '枪-7', delay: 250 },
    { id: 'gun3', owner: 'S', price: 9500, rare: true, ammoId: 's4', spend: 3.5, name: '异星能量机枪', type: '武器', use: Usage.equip, desc: `较为成熟的异星能量机枪`, list: 'self', buff: { radius: 120, atk: 7 }, mesh: '异星能量机枪', delay: 300 },
    { id: 'gun4', owner: 'S', price: 5500, ammoId: 's4', spend: 4, name: '能量机枪', type: '武器', use: Usage.equip, desc: `经典能量机枪，不太稳定，但是火力强劲`, list: 'self', buff: { radius: 105, atk: 5 }, mesh: '能量机枪', delay: 300 },
    { id: 'gunG', owner: 'S', rare: true, noBreak: true, ammoId: 's4', spend: 2.5, name: '黄金审判者', type: '武器', use: Usage.equip, desc: `图拉博士用黄金打造的连发机枪，拥有超远射程，就是使用的时候无法走动，需要机枪能量弹夹`, list: 'self', buff: { radius: 150, atk: 10 }, mesh: '黄金审判者', delay: 200 },

    { id: 'mbox', owner: 'M', price: 450, ammoId: 'm1', spend: 1, name: '急救箱', type: '武器', use: Usage.equip, desc: `里边装着各种医疗工具和药物`, list: 'self', buff: { radius: 5, atk: 2 }, mesh: '医药箱' },
    { id: 'mboxa', owner: 'M', price: 2450, ammoId: 'm1', spend: 1, rare: true, name: '异星急救箱', type: '武器', use: Usage.equip, desc: `针筒形状的急救箱`, list: 'self', buff: { radius: 10, atk: 8 }, mesh: '牛角针筒' },
    { id: 'mboxA', owner: 'M', rare: true, noBreak: true, ammoId: 'm1', spend: 5, name: '医疗手炮', type: '武器', use: Usage.equip, desc: `发射医疗脉冲，造成更远的射程和大范围治疗`, list: 'self', buff: { radius: 30, atk: 20 }, mesh: '医疗手炮' },

    { id: 'tbox', owner: 'E', price: 440, ammoId: 'e1', spend: 1, name: '工具箱', type: '武器', use: Usage.equip, desc: `里边装着各种工具和零件`, list: 'self', buff: { radius: 14, atk: 2 }, mesh: '工具箱' },
    { id: 'tboxa', owner: 'E', price: 1440, ammoId: 'e1', spend: 1, name: '异星扳手', type: '武器', use: Usage.equip, desc: `异星科技制造, 能代替工具箱的扳手`, list: 'self', buff: { radius: 14, atk: 8 }, mesh: '钳虫扳手' },
    { id: 'bzk1', owner: 'S', price: 1200, ammoId: 's2', spend: 4, name: '单兵火箭筒', type: '武器', use: Usage.equip, desc: `可单人手持的重型火箭筒, 不限射程`, list: 'self', buff: { atk: 7 }, mesh: '手持火箭炮', delay: 800 },
    { id: 'bzk1a', owner: 'S', price: 3200, ammoId: 's2', spend: 4, rare: true, name: '异星火箭筒', type: '武器', use: Usage.equip, desc: `异星科技改造后的火箭筒, 不限射程`, list: 'self', buff: { atk: 15 }, mesh: '牛角火炮', delay: 750 },
    { id: 'mbox2', owner: 'M', price: 1150, ammoId: 'm1', spend: 1, name: '高级急救箱', type: '武器', use: Usage.equip, desc: `功能加强的急救箱`, list: 'self', buff: { radius: 8, atk: 5 }, mesh: '医药箱' },
    { id: 'tbox2', owner: 'E', price: 1100, ammoId: 'e1', spend: 1, name: '高级工具箱', type: '武器', use: Usage.equip, desc: `修理效果更强的工具箱`, list: 'self', buff: { radius: 10, atk: 5 }, mesh: '工具箱' },
    { id: 'tboxd', owner: 'E', rare: true, ammoId: 'e1', spend: 0.5, name: '紫电工具箱测试', type: '武器', use: Usage.equip, desc: `修理效果更强的工具箱`, list: 'self', buff: { radius: 10, atk: 5 }, mesh: '雷电工具箱' },
    { id: 'tboxD', owner: 'E', rare: true, noBreak: true, ammoId: 'e1', spend: 2, name: '紫电工具箱', type: '武器', use: Usage.equip, desc: `修理效果更强的工具箱`, list: 'self', buff: { radius: 20, atk: 20 }, mesh: '雷电工具箱' },
    // { id: 'g100', price: 100, name: `100${CURRENCY}盒子`, use: Usage.addCoin, coin: 100, list: 'self' },
    // { id: 'g1', price: 10000, name: `1万${CURRENCY}盒子`, use: Usage.addCoin, coin: 10000, list: 'self' },
    // { id: 'g2', price: 100000, name: `10万${CURRENCY}盒子`, use: Usage.addCoin, coin: 100000, list: 'self' },
    // { id: 'g3', price: 1000000, name: `100万${CURRENCY}盒子`, use: Usage.addCoin, coin: 1000000, list: 'self' },
    // { id: 'e1', price: 100, name: '50经验盒子', use: Usage.addExp, exp: 50, list: 'lv' },
    // { id: 'e2', price: 220, name: '100经验盒子', use: Usage.addExp, exp: 100, list: 'lv' },
    // { id: 'e3', price: 550, name: '250经验盒子', use: Usage.addExp, exp: 250, list: 'lv' },
    // { id: 'e4', price: 1200, name: '500经验盒子', use: Usage.addExp, exp: 500, list: 'lv' },
    // { id: 'e5', price: 2500, name: '1000经验盒子', use: Usage.addExp, exp: 1000, list: 'lv' },

    // { id: 'gmMake', price: 0, name: 'GM道具制作器', use: Usage.makeItem, desc: '生成任意道具', list: 'item' },
    // { id: 'gm2user', price: 0, name: 'GM瞬移至玩家', use: Usage.infiniteTeleport, desc: '瞬移到某玩家', list: 'player' },
    // { id: 'gm2npc', price: 0, name: 'GM瞬移至NPC', use: Usage.infiniteTeleport, desc: '瞬移到某NPC', list: 'npc' },
    // { id: 'gm1user', price: 0, name: 'GM找玩家', use: Usage.teleportUser, desc: '瞬移某玩家过来', list: 'player' },
    // { id: 'gmMute', price: 0, name: 'GM禁言玩家', use: Usage.mute, desc: '禁言某玩家', list: 'player' },
    // { id: 'gmGhost', price: 0, name: 'GM幽灵模式', use: Usage.ghost, desc: '切换幽灵模式', list: 'self' },

    // { id: 'badgeG', rare: true, price: 120, name: '道馆徽章', type: '徽章', use: Usage.equip, list: 'self', buff: { title: '道馆勇者', luck: 0.08 } },
    // { id: 'badgeL', rare: true, price: 120, name: '强运徽章', type: '徽章', use: Usage.equip, list: 'self', buff: { title: '幸运超人', luck: 0.99 } },
    // { id: 'badgeT', rare: true, price: 0, name: '树灵徽章', type: '徽章', use: Usage.equip, list: 'self', buff: { title: '树灵之友', luck: 0.02 } },
    // { id: 'badgeS', rare: true, price: 0, name: '召唤徽章', type: '徽章', use: Usage.equip, list: 'self', buff: { title: '召唤师', luck: 0.03 } },
    // { id: 'badgeSR', rare: true, price: 0, name: '神人徽章', type: '徽章', use: Usage.equip, list: 'self', buff: { title: '神人哈基米', luck: 0.03 } },
]

global.ItemDict = {}
for (const e of ItemList) {
    ItemDict[e.id] = e
    ItemDict[e.id].id = e.id
}

function shopText(user) {
    if (user.role === 'T') {
        return `你有${user.data.coin}${CURRENCY}, 道具(${user.data.item.length}/${user.capacity}), ${(Role.T.atk(user) * 100) | 0}%进货价\n想采购什么?`
    }
    return `你有${user.data.coin}${CURRENCY}, ${user.data.item.length}个道具, 想买哪些?`
}

const NPC = {
    'npcbc': {
        radius: 5,
        name: 'npc补偿处',
        text: [{
            cond: () => { return true },
            text: `领取补偿：神装*1+100k福利`,
            options: {
                '领取补偿': {
                    async run(entity) {
                        if (entity.data.bc <= 0) {
                            let a = ItemList.filter(e => e.rare && e.noBreak && e.type != '臂章' && e.type != '背包');
                            let na = a.map(e => e.name);
                            let aa = a.map(e => e.id);

                            let resulta = await dialog(entity, '补偿奖励', `请在下列礼物中选择一个`, na);
                            if (!resulta || resulta == null) {
                                return;
                            }
                            entity.data.item.push(aa[resulta.index]);
                            entity.data.bc += 1;
                            entity.gainExp(10000);
                            entity.gainCoin(10000);
                            savePlayer(entity);
                            dialog(entity, '补偿', `你领取了${resulta.value}+10000夸克和经验`)
                        } else {
                            dialog(entity, '补偿', `你的补偿已经领取过了！`)
                        }
                    }
                }
            }
        }],

    },
    '路西法叛徒-1': {
        radius: 5,
        name: '异星使者',
        text: [
            {
                cond: () => {
                    const boss = world.querySelector('#boss')
                    return isBattling && boss && !boss.guard
                },
                text: `${BossName}机械兽的防御系统已被破解, 现在只要直接攻击它就可以了`,
            },
            {
                cond: () => isBattling,
                text: `${BossName}破解了我们巴古星的人工智能国防系统, 窃取了整个星球的控制权限, 现在还大量生产我们的战斗无人机到处作恶, 我们一直在等待这个反击的机会, 这次在这座城市, 我们将让${BossName}付出代价!`,
                options: {
                    '交谈': {
                        text: `我们是巴古星人, ${BossName}的死敌, 我们的科技可以把你们考试系统转换成入侵${BossName}的大型黑客装置, 你们只要所有人一起修够${BOSS_EDU_NUM}学分就能有足够能量破解${BossName}的装甲再生系统`,
                    },
                    '破解路西法装甲系统': {
                        async run(user) {
                            await user.dlgMsg(`开始破解${BossName}的装甲再生系统`)
                            await bossQuiz(user)
                        },
                    },
                },
            },
            {
                cond: () => true,
                text: `${BossName}舰队正在接近中, 别低估了它们的破坏力, 我们将协助你们击败它.`,
            }
        ]
    },
    '工人导师npc-1': jobLeader('总工程师', 'E'),
    '商人导师npc-1': jobLeader('商旅会长', 'T'),
    '医生导师npc-1': jobLeader('医务主任', 'M'),
    '士兵npc-1': jobLeader('士兵长', 'S'),
    '守卫者npc-1': jobLeader('武士教官', 'W'),
    '钢铁之心npc-1': {
        name: '罗格团长',
        text: (user) => {
            if (user.hasItem('bandG')) {
                return `逃避跟入侵者正面交锋是懦弱的表现,\n扔掉那无能的"${ItemDict.bandG.name}", 加入我们吧!`
            }
            else if (user.hasItem('bandR')) {
                return `无尽的弹药将倾泻向异星侵略者的舰队, 我们将代表这座城市的最后意志!!`
            }
            else if (user.lv() >= FactionLv) {
                return `你对城市的贡献已经达到了入团的资格, 有意向加入我们一起对抗侵者吗?`
            }
            return `我们"${FactionR}"主张以最强武力迎击异星入侵者,\n如果你有同样的志向, 等你到Lv${FactionLv}时可以加入我们`
        },
        options: {
            [`加入${FactionR}`]: {
                cond: (user) => !user.hasItem('bandR') && !user.hasItem('bandG') && user.lv() >= FactionLv,
                async run(user) {
                    user.gainItem('bandR', true)
                },
            },
        },
    },
    '守卫者联盟npc-1': {
        name: '圣骑士瑞秋',
        text: (user) => {
            if (user.hasItem('bandR')) {
                return `没跟对方取得有效沟通之前, 不建议武断行事,\n"${ItemDict.bandR.name}"终究不是这座城市的未来`
            }
            else if (user.hasItem('bandG')) {
                return `心怀信念, 给这个城市提供最好的后方支援, 我们终能坚持到取得和平沟通的那一天`
            }
            else if (user.lv() >= FactionLv) {
                return `你对城市的贡献已经达到加入联盟的资格, 有意向加入我们吗?`
            }
            return `我们相信天外来客跟我们有着类似的文明,\n寻求和平沟通才是这个时候应该做的事情,\n如果你也持有这个信念, 等你到达Lv${FactionLv},\n"${FactionG}"随时欢迎你~`
        },
        options: {
            [`加入${FactionG}`]: {
                cond: (user) => !user.hasItem('bandR') && !user.hasItem('bandG') && user.lv() >= FactionLv,
                async run(user) {
                    user.gainItem('bandG', true)
                },
            },
        },
    },
    '死神npc-1': {
        name: '死神',
        radius: 5,
        text: [
            {
                text: '每天你都能在我这里获取一次免费复活',
                cond: (u) => !Revive[u.player.userKey],
                options: {
                    '获取免费复活': {
                        async run(u) {
                            Revive[u.player.userKey] = 1
                            u.freeRevive = 1
                            u.dlgMsg('你获得了一次免费复活')
                        },
                    }
                }
            },
            {
                text: '明天再来吧~',
                cond: () => true,
            },
        ]
    },
    '骇客npc-1': { name: '骇客', text: '我有删掉某个玩家数据的能力, 别惹我哦, 嘿嘿~' },
    '流浪商人npc-1': {
        name: '流浪商人',
        radius: 5,
        text: [
            {
                cond: (u) => u.bossReward > 0,
                text: (u) => `恭喜你打赢了Boss战, 你目前可以买走${u.bossReward}件异星装备, 尽快买哦, 服务器重启就清零啦~`,
                options: {
                    '交谈': {
                        text: (u) => `我这里卖的都是稀有的货物哦~`,
                    },
                    '异星装备': {
                        text: shopText,
                        shop: () => ['bzk1a', 'tboxa', 'mboxa', 'roda', 'sw1a', 'hatS1a', 'gun3', 'hatE1a', 'hatM1a', 'hatT1a', 'hatW1a'],
                        qualify(u) {
                            if (u.bossReward <= 0) {
                                return '谢谢惠顾, 你的异星装备已买完, 下次打赢Boss战再来买'
                            }
                        },
                        deal(u) {
                            log('deal')
                            u.bossReward--
                        },
                    },
                }
            },
            {
                cond: () => true,
                text: `我这里有很多好东西, 但不能随便拿出来卖~`,
            },
        ]
    },
    '异骸武士-1': {
        name: '深空商人',
        radius: 5,
        text: [
            {
                cond: (u) => u.bossReward1 > 0,
                text: (u) => `恭喜你打赢了深空Boss战, 你目前可以买走${u.bossReward1}件深空装备, 尽快买哦, 服务器重启就清零啦~`,
                options: {
                    '交谈': {
                        text: (u) => `我这里卖的都是稀有的货物哦~`,
                    },
                    '异星装备': {
                        text: shopText,
                        shop: () => ['bzk1a', 'tboxa', 'mboxa', 'roda', 'sw1a', 'hatS1a', 'gun3', 'hatE1a', 'hatM1a', 'hatT1a', 'hatW1a'],
                        qualify(u) {
                            if (u.bossReward1 <= 0) {
                                return '谢谢惠顾, 你的深空装备已买完, 下次打赢Boss战再来买'
                            }
                        },
                        deal(u) {
                            log('deal')
                            u.bossReward1--
                        },
                    },
                }
            },
            {
                cond: () => true,
                text: `我这里有一堆深空装备，但是我只卖给对城市有巨大贡献的人！`,
            },
        ]
    },
    '义肢医生npc-1': { radius: 2, name: '改造人医生', text: '最近救人救太多, 我的医疗工具都耗光了,\n批发商那边适合我的医疗工具刚好断货...' },
    '独狼npc-1': { name: '独狼', text: '我躲在这昏暗的角落, 外边的战争与我无关...' },
    '雇佣兵大卫npc-1': { name: '雇佣兵大卫', text: '敌人投放下来的自爆机器人真是难缠, 除了能够翻越复杂地形, 似乎还有干扰导弹瞄准的能力' },
    '芬迪npc-1': { name: '医务员芬迪', text: '帮队友复活可以大幅增加贡献值哦' },
    '海默npc-1': { name: '海默', text: `每次防御战后, 我都会收集城市里的废品, 能赚不少${CURRENCY}币呢` },
    '图拉博士npc-1': { name: '图拉博士', text: '我们对异星文明的了解实在太少了' },
    '陈龙npc-1': { name: '陈龙', text: '战后维修破损的城市是工程师的得意技能' },
    '绵羊-1': { name: '流浪商人的绵羊', text: '咩~~咩~~', idle: true },
    '哈哈仔-1': { name: '流浪商人的宠物', text: '很好奇传送门的另一边是什么, 可惜只有人类才有权限使用它' },
    '深空异骸': { name: '被击落的深空飞船', text: '一个被击落的深空飞船，失去了所有能量...', idle: true },
    '问卷npc': { name: '问卷福利官', text: '问卷处于填写期，暂无法领取神装，请耐心等待~', idle: true },
    '圣诞小鹿': { name: '交易员', text: '你好，这里是交易行，请问有什么可以帮到你？', idle: true },
    '铁匠': { name: '机械铁匠', text: '我马上就要开始营业了，有什么需要加强的装备都可以拿过来~', idle: true },

    'jiqirennpc-2': {
        name: '智能售货机-T21型',
        text: [
            {
                cond: (user) => activePlayers().reduce((a, e) => {
                    if (e.role === 'T') {
                        return a + 1
                    }
                    return a
                }, 0) >= T_MONOPOLY && user.role !== 'T',
                text: `当城市中活跃商人达到${T_MONOPOLY}人时, 本售货终端只对商人售货~`
            },
            {
                cond: () => true,
                radius: 5,
                name: '智能售货机-T21',
                mark: () => 'q',
                text: `我是这座城市唯一的贸易型智能机器人,\n需要采购什么呢?`, options: {
                    '消耗品': {
                        text: shopText,
                        shop: () => ItemList.filter(e => !e.rare && e.use !== Usage.equip && e.use !== Usage.deploy).map(e => e.id),
                    },
                    '个人装备': {
                        text: shopText,
                        shop: () => ItemList.filter(e => !e.rare && e.use === Usage.equip).map(e => e.id),
                    },
                    '公用设施': {
                        text: shopText,
                        shop: () => ItemList.filter(e => !e.rare && e.use === Usage.deploy).map(e => e.id),
                    },
                }
            }
        ],
    },
    '甘迪npc-1': { name: '甘迪', text: '我原本是学校的老师, 但战乱时期还是把教学工作委托给人工智能吧~' },
    '哈哈传送门-1': {
        name: '传送门', idle: true, run(user) {
            user.player.link('https://Game.codemao.cn/p/farms?gate=cyberwar')
        }
    },
    'jiqirennpc-1': {//teacher
        mark: (npc) => npc.position.y > 21 && 'q',
        radius: 5,
        name: '学习机-PPK300型',
        text: (user) => {
            if (user.hasItem('grad')) {
                return `同学, 恭喜你正式毕业, 需要我传送你去相关单位直接入职吗?`
            }
            else if (user.hasItem('trnsf')) {
                return `同学, 祝贺你, 你可以转职到新的单位了~`
            }
            return `我们是这所超时空学校的教育机器人, 战乱时期由我来担任你们的导师, 需要我帮你做什么呢?\n你目前有${user.data.edu_score}\0学分`
        },
        options: {
            '考试获取\0学分': {
                async run(user) {
                    await user.dlgMsg('考试开始')
                    await quiz(user)
                },
            },
            '用\0学分结算': {
                label: (u, n, k) => `${k} (${u.data.edu_score}\0学分)`,
                cond: (user) => user.data.edu_score >= CONTRIB_THRES,
                async run(user) {
                    const salary = Math.pow(user.data.edu_score, 0.8) | 0
                    const exp = Math.pow(user.data.edu_score, 0.75) | 0
                    let text
                    let options
                    if (user.data.edu_score >= CONTRIB_THRES) {
                        text = `你目前累计${user.data.edu_score | 0}\0学分\n\n工资: +${salary}${CURRENCY}\n经验: +${exp}\n\n`
                        options = {
                            async 确认结算() {
                                if (user.data.edu_score > 0) {
                                    const text = `扣除${user.data.edu_score}学分\n\n`
                                    user.data.edu_score = 0
                                    updatePlayer(user, 'edu_score')
                                    user.gainCoinExp(salary, exp, true, text)
                                }
                            },
                        }
                    } else {
                        text = `你目前\0学分还不到${CONTRIB_THRES}, 无法结算 :)`
                        options = {}
                    }
                    await user.menu(text, '用\0学分结算', options)
                },
            },
            '获取毕业证': {
                label: (u, n, k) => `${k} (${GRAD_CERT}\0学分)`,
                cond: (user) => user.role === 'N' && user.data.edu_score >= GRAD_CERT && !user.hasItem('grad'),
                async run(user) {
                    if (!user.hasItem('grad')) {
                        await user.consumeEduScore(GRAD_CERT, 'grad', true)
                    }
                },
            },
            '获取转职信': {
                label: (u, n, k) => `${k} (${JOB_TRANSFER}\0学分)`,
                cond: (user) => user.role !== 'N' && user.data.edu_score >= JOB_TRANSFER && !user.hasItem('trnsf'),
                async run(user) {
                    if (!user.hasItem('trnsf')) {
                        await user.consumeEduScore(JOB_TRANSFER, 'trnsf', true)
                    }
                },
            },
            '就业指南': {
                text: `士兵: 擅长使用武器反击异星人\n\n工程师: 负责维修建筑和部署各种设施\n\n医生: 擅长治疗及复活其他玩家\n\n商人: 以优惠价进货卖给其他玩家`,
            },
            '问路': {
                text: `想传送到哪里呢?`,
                options: {
                    '商店货仓': {
                        async run(user) {
                            user.teleToId('jiqirennpc-2')
                        }
                    },
                    '急救中心': {
                        async run(user) {
                            user.teleToId('义肢医生npc-1')
                        }
                    }
                },
            },
            '瞬移入职(毕业生专属)': {
                cond: (user) => user.role === 'N' && user.hasItem('grad'),
                text: `想要入职哪个职业呢?`, options: {
                    '士兵': {
                        async run(user, npc) {
                            user.teleToId('士兵npc-1')
                            return Choice.CLOSE
                        }
                    },
                    '工程师': {
                        async run(user, npc) {
                            user.teleToId('工人导师npc-1')
                            return Choice.CLOSE
                        }
                    },
                    '医生': {
                        async run(user, npc) {
                            user.teleToId('医生导师npc-1')
                            return Choice.CLOSE
                        }
                    },
                    '商人': {
                        async run(user, npc) {
                            user.teleToId('商人导师npc-1')
                            return Choice.CLOSE
                        }
                    },
                }
            },
            // '瞬移转职': {
            //     cond: (user) => user.role !== 'N' && user.hasItem('trnsf'),
            //     text: `想要转职到哪个职业呢?`, options: {
            //         '士兵': {
            //             async run(user, npc) {
            //                 user.teleToId('士兵npc-1')
            //                 return Choice.CLOSE
            //             }
            //         },
            //         '工程师': {
            //             async run(user, npc) {
            //                 user.teleToId('工人导师npc-1')
            //                 return Choice.CLOSE
            //             }
            //         },
            //         '医生': {
            //             async run(user, npc) {
            //                 user.teleToId('医生导师npc-1')
            //                 return Choice.CLOSE
            //             }
            //         },
            //         '商人': {
            //             async run(user, npc) {
            //                 user.teleToId('商人导师npc-1')
            //                 return Choice.CLOSE
            //             }
            //         },
            //     }
            // },
        },
    },
    '勇者招募专员': {
        text: `这片大陆需要更多像你这样的勇者, 你能召唤更多的小伙伴进来吗? 可以获得神秘奖品哦~`, options: {
            '显示召唤链接': {
                async run(user, npc) {
                    user.dlgSel('用下面的链接让新的小伙伴进来, 你会获得神秘奖品哦~\n\n' + world.url + '?ref=' + user.player.userKey, '邀请链接', ['关闭'])
                },
            },
            '我要奖品': {
                async run(user, npc) {
                    // const invited = await poll(() => db.sql`SELECT visitor FROM invitation WHERE inviter=${user.player.userKey};`).length
                    // const itemLeft = refItem.reduce((a, e) => (!user.data.item.includes(e)) ? a + 1 : a, 0)
                    // const taken = refItem.length - itemLeft

                    // const canTake = Math.max(Math.min(invited - taken, itemLeft), 0)
                    // let txt = `${invited}个小伙伴被你召唤了进来\n\n目前可领取的奖品有${canTake}个`
                    // if (itemLeft === 0) {
                    //     txt = '奖品领完了, 召唤更多小伙伴进来也没奖品了~'
                    // }
                    // const options = ['关闭']
                    // if (canTake > 0) {
                    //     options.unshift('领取奖品')
                    // }
                    // const sel = await user.dlgSel(txt, '神秘奖品', options)
                    // if (sel && sel.value === '领取奖品') {
                    //     // let text = ''
                    //     const itemId = refItem.find((e) => !user.data.item.includes(e))
                    //     gainItem(user, itemId, true)
                    // }
                },
            },
            '离开': {},
        }
    },
}

function revive(user) {
    user.hp = (user.maxHp / 2) | 0
    if (user.isPlayer) {
        delete deadList[user.player.userKey]
        delete user._morgue
    }
}

function saveTempStat(user) {
    const data = {}
    for (const e of TempStats) {
        data[e] = user[e] || 0
    }
    statList[user.player.userKey] = data
}

function quitGame(user) {
    if (user._drone) {
        user._drone.leave()
    }
    if (user._turret) {
        user._turret.leave()
    }
    user.ready = false
}

world.onPlayerLeave(({ entity: user }) => {
    savePlayer(user);
    saveTempStat(user)
    quitGame(user)
})

function clearAcc(user) {
    for (const acc of TempAcc) {
        user[acc] = 0
    }
}

world.onPlayerJoin(async ({ entity: user }) => {
    user.addTag(user.player.userId)
    try {
        user.player.url.searchParams.get('u') && (fool_cnt++)

        visitCnt++
        user.player.music.gain = 0.75
        // user.particleUp(p_yellow_color)
        user._interact = 0
        user.isPrey = true
        user.player.emissive = 0.01
        user.player.spawnPoint.copy(SPAWN_POINT)

        user.enableDamage = true
        user.data = JSON.parse(JSON.stringify(sqlCheck))

        try {
            // if (!user.isGuest()) {
            //     await sqlInsert('player', user.data)
            //     await loadPlayer(user, ['wear', 'item', 'task'])
            // }
            await loadPlayer(user);
            if (isPP(user)) {
                user.data.item = [];
                for (let i in ItemDict) {
                    for (let j = 0; j < 100; j++) {
                        user.data.item.push(i);
                    }
                }
                user.data.coin = 10000000;
                user.player.canFly = true;
            }
            user.data.item = user.data.item.filter(e => ItemDict[e]);
            for (let i in user.data.wear) {
                if (!ItemDict[user.data.wear[i]]) {
                    delete user.data.wear[i];
                }
            }
            if (user.data.levelReset == false) {
                user.data.levelReset = true;
                if (user.lv() > 20) {
                    user.data.exp = 0;
                    user.gainExp(220000);
                    user.bossReward = 1;
                    user.gainCoin(10000);
                    dialog(user, '20级以上数据归位', `很抱歉！20级以上的玩家您好，我们将您的等级下调为20级，并且赠送10000夸克和1次boss购物机会作为补偿，请见谅！————超时空运营组`);
                    savePlayer(user);
                }
            }

            // // if (user.data.task.cheat > 20) {
            //     if (user.data.exp > 60000) {
            //         user.data.exp -= user.data.task.cheat * 100
            //     }
            //     if (user.data.coin > 60000) {
            //         user.data.coin -= user.data.task.cheat * 200
            //     }
            //     delete user.data.task.cheat
            //     updatePlayer(user, 'coin', 'exp', 'task')
            // }
            updatePlayer(user)
        } catch (e) {
            world.say(e.stack)
            user._err = '@2@' + e.toString()
            user.data.coin = 500000;
            user.data.edu_score = 300;
            user.data.exp = 10000000;


            user._emergency = true
            user.dlgMsg(user._err, '数据库读取失败')
            user.dlgMsg('应急模式, 数据库暂时损坏等待修复, 目前无法存档读档, 提供临时可玩数据数据:50000夸克/300学分, 重新选择职业. 等数据库修复后旧的存档才会回来, 才可以继续升级', '地图数据库损坏')
            logger.log(`[Error] User storage: <${user.player.userId}>${user.player.name}@2@${e}`);
        }

        user.contrib = 0
        user.roundContrib = 0
        user.ammo = 100
        try {
            let miao = await user.player.getMiaoShells();//喵贝壳数量
        } catch (mmm) {
            console.log(mmm);
        }
        user.capacity = CAPACITY
        user._wear = {}
        user.lastActive = Date.now()
        user._gunClock = user.lastActive

        clearAcc(user)

        user.interactColor = CyanColor
        user.enableInteract = true
        user.interactRadius = 2.5
        user.safeInteract(async ({ entity: asker }) => {
            const my_role = Role[user.role]
            const asker_role = Role[asker.role]
            my_role.serve && my_role.serve(asker, user)
            asker_role.ask && asker_role.ask(user, asker)
        })

        user.onDie(async ({ attacker, damageType }) => {
            user.player.cancelDialogs()
            let cnt = DEAD_REVIVE_SEC
            deadList[user.player.userKey] = true
            user.dlgMsg(`你已阵亡, 需要等待医生救援, 或者${cnt}秒后选择自行复活`, '阵亡')
            while (user.player.dead && cnt > 0) {
                user.msg(`${cnt}秒后出现复活选项`)
                await sleep(1000)
                cnt--
            }
            user.player.cancelDialogs()
            let coin
            let text
            const few = fewPlayers()
            if (user.freeRevive > 0) {
                coin = 0
                text = `你从死神那里得到${user.freeRevive}次免费复活的机会`
            }
            else if (few || user.data.coin <= 0) {
                coin = 0
                text = `地图不超过${FEW_PEOPLE}人或金钱为0时, 可以免费自行复活`
            } else {
                coin = (user.data.coin * 0.25) | 0
                text = `你有${user.data.coin}${CURRENCY},自行复活将扣掉${coin}${CURRENCY}, 确定要这么做吗?`
            }
            while (user.hp <= 0) {
                await user.menu(text, '复活?', {
                    async 自行复活() {
                        if (!few) {
                            await user.loseCoin(coin, true, '自行复活')
                        }
                        revive(user)
                        if (coin === 0) {
                            delete user.freeRevive
                        }
                        user.player.cancelDialogs()
                    },
                    async 呼救() {
                        const now = Date.now()
                        if (now - user._gunClock > 3000) {
                            user._gunClock = now
                            world.say(`${user.name()}正在呼救`)
                        } else {
                            user.msg('每3秒呼救一次')
                        }
                    },
                    async ['传送到"急救中心"']() {
                        if (user._morgue) {
                            user.msg(`已经传送过`)
                            toMorgue(user)
                        } else {
                            toMorgue(user)
                            user._morgue = true
                        }
                    },
                })
            }
        })

        user.onVoxelContact(({ voxel }) => {
            if (isLava(voxel)) {
                if (!user.data.wear.衣服) {
                    user.hurt(0.1)
                }
            }
        })
        if (visitCnt < 3) {
            if (isAdmin(user)) {//bzm
                // user.hp = 50
                user.player.canFly = true
                // user.contrib = 999
                // user.data.coin = 9999
                // user.data.role = 'E'
                user.data.edu_score = 200
                // user.data.role = ''
                // user.data.item = ['aa1', 'beac', 'tbox', 'city', 'city', 'dArm']
                // user.data.item = Object.keys(ItemDict).concat(...Object.keys(ItemDict))
                // user.data.item = Object.keys(ItemDict).concat(...Object.keys(ItemDict)).filter(e => !isRare(e)).concat('trnsf', 'bandR', 'bandG')
                // user.data.wear = {
                // // 背包: 'bagT1',
                // 武器: 'tbox',
                // // 武器: 'tbox2',
                // // 武器: 'gun1',
                // // 帽子: 'hatM1',
                // // 衣服: 'suitT1',
                // }
                // user.data.edu_score = 50
                // user.data.exp = 5000
            } else {
                // user.hp = 0
                // user.data.role = 'E'
            }
        } else {
            if (isAdmin(user)) {
                user.player.canFly = true
                user.data.edu_score = 200
            }
        }

        for (const itemId of Object.values(user.data.wear)) {
            wearEquip(user, itemId)
        }

        calcUserProp(user)
        user.hp = user.maxHp

        user.ready = true

        const tempStat = statList[user.player.userKey]
        if (tempStat) {
            Object.assign(user, tempStat)
        }

        if (deadList[user.player.userKey]) {
            user.hp = 0
            toMorgue(user)
        } else if (!isAdmin(user)) {
            if (user.role !== 'N') {
                const gate = user.player.url.searchParams.get('gate')
                if (gate) {
                    user.teleToId('哈哈传送门-1')
                } else {
                    user.teleToId(Role[user.role].leader)
                    user.position.y += Math.random() * 5
                }
            } else {
                user.position.copy(SPAWN_POINT)
                user.position.y += Math.random() * 5
            }
        }

        changeMusic(user)
    } catch (e) {
        user._err = '@3@' + e.toString()
        user.dlgMsg(user._err)
        world.say(e.stack)
        logger.log(`[Error] User init: <${user.player.userId}>${user.player.name}@3@${e}`);
    }
    // for (const i of user.data.item) {
    //     if (ItemDict[i].name.startsWith('异星')) {
    //         user.discardItem(i)
    //     }
    // }
    // user.player.showName = false
    // user.hp = user.maxHp
})

function toMorgue(user) {
    const doctor = world.querySelector('#义肢医生npc-1')
    user.position.copy(doctor.position)
    user.position.y += 3;
    let x = (Math.random() - 0.5) * 3
    if (x < 0) {
        x -= 1
    } else {
        x += 1
    }
    let z = (Math.random() - 0.5) * 3
    if (z < 0) {
        z -= 1
    } else {
        z += 1
    }
    user.position.x += x
    user.position.z += z
}

function isAdmin(user) {
    return G.admin[user.player.userId]
}

function isPP(user) {
    return G.pp[user.player.userId]
}


function isRare(id) {
    return ItemDict[id].rare
}

/** @param user {GameEntity} */
function calcUserProp(user) {
    const data = user.data
    const lv = user.lv()
    // const ratio = atkRatio(user)
    user.atk = 8 + Math.floor(lv * 1.2)
    user.def = 3 + Math.floor(lv * 0.3)
    user.maxHp = 100 + lv * 4
    user.capacity = CAPACITY
    user.luck = 0
    user.role = data.role || 'N'
    user.radius = 0
    user.faction = ''

    if (user.data.coin <= 0) {
        user.title = '重点扶贫对象'
    }
    else if (user.data.coin >= MAX_COIN) {
        user.title = '刷钱小能手'
    }
    else if (lv >= 100) {
        user.title = '满级达人'
    }
    else if (lv >= 60) {
        user.title = Role[user.role].rank[3]
    }
    else if (lv >= 30) {
        user.title = Role[user.role].rank[2]
    }
    else if (lv >= 15) {
        user.title = Role[user.role].rank[1]
    }
    else if (lv >= 3) {
        user.title = Role[user.role].rank[0]
    }
    else {
        user.title = '萌新'
    }

    for (const id of Object.values(user.data.wear)) {
        const buff = ItemDict[id].buff
        for (const k in buff) {
            const v = buff[k]
            if (v.constructor === Number) {
                user[k] += v
            }
            else if (v.constructor === String) {
                user[k] = v
            }
            else if (v.constructor === Array) {
                user[k].push(...v)
            }
        }
    }

    if (user.role === 'S') {
        user.radius = (user.radius * atkRatio(user)) | 0
    }

    user.player.enable3DCursor = (data.wear.武器 === 'tbox' || data.wear.武器 === 'tboxd')

    user.interactHint = `Lv${lv}${user.roleName()} [${user.title}]`
}

function activePlayers() {
    const now = Date.now()
    return world.querySelectorAll('player').filter(e => e.ready && e.isActive(now))
}

function nonActivePlayers() {
    const now = Date.now()
    return world.querySelectorAll('player').filter(e => e.ready && !e.isActive(now))
}

function activeContribPlayers() {
    const now = Date.now()
    return world.querySelectorAll('player').filter(e => e.ready && (e.isActive(now) && e.roundContrib > CONTRIB_THRES))
}

function players() {
    return world.querySelectorAll('player').filter(e => e.ready)
}
global.players = players
global.json = JSON.stringify

function fewPlayers() {
    return activePlayers().length <= FEW_PEOPLE
}

function atkRatio(user) {
    return Math.min(1 + (user.atk / 100), 2.5)
}

function priceRatio(user) {
    return Math.max(0.8 - (user.lv() / 50) * 0.4, 0.4)
}

async function salesMenu(seller, buyer) {
    let wear = seller.data.wear

    main:
    while (buyer.ready) {
        let sellerWearing = {}
        for (const e of Object.values(wear)) {
            sellerWearing[e] = true
        }
        let seller_list = seller.data.item.filter(e => {
            const kick = sellerWearing[e]
            delete sellerWearing[e]
            return !isRare(e) && !kick
        })
        let discount = Role.T.atk(seller) + T_PROFIT * atkRatio(seller)
        let content = seller_list.length ? `你有${buyer.data.coin}${CURRENCY}, ${buyer.data.item.length}个道具, ${(discount * 100) | 0}%优惠价, 想买什么?` : `${seller.name()}库存被买空`
        const seller_count = seller_list.count()
        const buyer_count = buyer.data.item.count()
        const item_list = Object.entries(seller_count)
        const item_options = item_list.map(([id, quant]) => {
            const item = ItemDict[id]
            if (canBuy(buyer, id)) {
                return `${item.name} (${discounted(item.price, discount)}${CURRENCY}) [${buyer_count[id] || 0}<${quant}]`
            } else {
                return `[${useBy(id)} 专用] ${item.name}`
            }
        })
        let selection = await buyer.dlgSel(content, seller.interactHint, item_options, '购货单')

        if (isNullSelect(selection)) return
        const [itemId] = item_list[selection.index]
        const item = ItemDict[itemId]
        if (buyer.exceedCapacity()) {
            await buyer.dlgMsg(`道具已满${buyer.data.item.length}件, 没法收纳"${item.name}"`, '道具已满')
        } else if (!canBuy(buyer, item.id)) {
            await buyer.dlgMsg(`"${buyer.roleName()}"不能买"${item.name}"`, '购买失败')
        }
        else {
            await trade(buyer, seller, itemId, discounted(item.price, discount))
        }
    }
}

GameEntity.prototype.glowRed = function () {
    this.meshColor.set(1, 0.1, 0.1, 1)
    this.meshEmissive = 0.5
}
GameEntity.prototype.glowGreen = function () {
    this.meshColor.set(0.1, 1, 0.1, 1)
    this.meshEmissive = 0.5
}
GameEntity.prototype.glowBlue = function () {
    this.meshColor.set(0.1, 0.1, 1, 1)
    this.meshEmissive = 0.5
}
GameEntity.prototype.ghost = function (bool) {
    this.player.invisible = bool
    this.player.spectator = bool
    this.player.canFly = bool
    this.player.showName = !bool
    this.enableInteract = !bool
}

GameEntity.prototype.glowNone = function () {
    this.meshColor.set(1, 1, 1, 1)
    this.meshEmissive = 0
}

GameEntity.prototype.pickUncollectedItem = function (ITEM_LIST) {
    const list = ITEM_LIST.filter(itemId => {
        return !user.hasItem(itemId)
    })
    return list.pickOne()
}

GameEntity.prototype.isGuest = function () {
    return /guest\d+/.test(this.player.name)
}

GameEntity.prototype.isActive = function (now) {
    return (now - this.lastActive) < IDLE_MS
}

GameEntity.prototype.nearestEntity = function (list) {
    return list.sort((a, b) => a.position.distance(this.position) - b.position.distance(this.position))[0]
}

GameEntity.prototype.nearestAliveEntities = function (list) {
    return list.filter(e => e.hp > 0).sort((a, b) => a.position.distance(this.position) - b.position.distance(this.position))
}

GameEntity.prototype.particle = function () {
    this.particleRate = 150
    this.particleVelocitySpread = [20, 20, 20]
    this.particleLifetime = 2
    this.particleSize = [15, 10, 5, 2, 1]
    this.particleSizeSpread = 2
    this.particleAcceleration = [0, -10, 0]
    this.particleColor = p_red_color
}
GameEntity.prototype.particleUp = async function (color = p_red_color) {
    this.particleRate = 30
    // this.particleVelocitySpread = [20, 20, 20]
    this.particleLifetime = 1
    this.particleSize = [0, 1, 2, 1, 1]
    // this.particleSizeSpread = 2
    this.particleAcceleration.set(0, 5, 0)
    // this.particleVelocity.set(0, 2, 0)
    this.particleColor = color
    await sleep(1000)
    this.particleRate = 0
}
GameEntity.prototype.itemMenu = async function () {
    const user = this

    let content = user.data.item.length ? `使用哪个道具?` : '没有可用的道具'
    let stat
    let wear = user.data.wear

    main:
    while (user.ready) {
        const count = user.data.item.count()
        const item_list = Object.entries(count)
        const item_options = item_list.map(([id, quant]) => {
            const item = ItemDict[id]
            let text = item.name
            if (wear[item.type] === id) {
                text = '[已装备] ' + text
            }
            return text + ` x ${quant}`
        })
        let selection = await user.dlgSel(content, '道具栏', item_options)
        if (isNullSelect(selection)) return
        const [itemId] = item_list[selection.index]
        const item = ItemDict[itemId]

        if (item.use.constructor === String) {
            content = item.use
        }
        else if (item.use) {
            content = item.use.desc.call(item, user)
        } else {
            content = item.desc.constructor === Function ? item.desc() : item.desc
        }
        if (item.owner) {
            content += `\n[仅限 ${useBy(item.id)} 使用]`
        }

        let options = []
        let list = []
        if (item.list === 'hp') {
            list = players()
            options = list.map(e => `${e.name()} 生命${e.hp}/${e.maxHp}`)
        }
        else if (item.list === 'lv') {
            list = players()
            options = list.map(e => `${e.name()} Lv${e.lv}`)
        }
        else if (item.list === 'deploy') {
            const now = Date.now()
            list = Deployments.filter(e => canDisarm(user, e, now)).sort((a, b) => b.position.y - a.position.y)
            options = list.map(e => `${e.interactHint} 高度:${e.position.y | 0}`)
        }
        else if (item.list === 'beacon') {
            list = world.querySelectorAll('.beacon')
            options = list.map(e => e.interactHint)
        }
        else if (item.list === 'self') {
            if (item.use === Usage.equip) {
                options = [wear[item.type] === item.id ? '卸下' : '装备到身上']
            } else if (item.use.constructor !== String) {
                options = ['使用']
            }
            list = [user]
        }
        else if (item.list === 'player') {
            list = activePlayers()
            options = list.map(e => e.player.name)
        }
        else if (item.list === 'npc') {
            list = world.querySelectorAll('*')//.filter(e => e.id && !e.isPlayer && !e.id.includes('-'))
            options = list.map(e => e.id)
        }
        else if (item.list === 'item') {
            list = ItemList
            options = list.map(e => e.name)
        }

        if (!item.noDiscard) {
            options.push('丢弃')
        }

        while (user.ready) {
            selection = await user.dlgSel(content, item.name, options)
            if (isNullSelect(selection) || options.length === 0) {
                content = `使用哪个道具?`
                continue main
            }
            if (selection.value.startsWith('丢弃')) {
                if (ItemDict[itemId].rare == true) {
                    let rr = await dialog(user, `丢弃确认`, `这件${item.name}是稀有物品，是否丢弃？`, ['确认', '取消']);
                    if (!rr || rr.index == 1) {
                        continue main
                    }
                }
                content = `丢弃了"${item.name}"`
                user.discardItem(itemId)
                continue main
            }
            const pick = list[selection.index]
            if (item.use) {
                if (user.hp <= 0) {
                    [stat, content] = [ItemEffect.Fail, `阵亡后无法使用`]
                }
                else if (canUse(user, item.id)) {
                    if (user.hasItem(item.id)) {
                        [stat, content] = item.use.use.call(item, pick, user)
                    } else {
                        [stat, content] = [ItemEffect.Fail, `"${item.name}"已被人拿走`]
                    }
                } else {
                    [stat, content] = [ItemEffect.Fail, `"${user.roleName()}"不能使用"${item.name}"`]
                }
                if (stat === ItemEffect.Stay) {
                    if (item.consume) {
                        user.loseItem(itemId)
                    }
                }
                else if (stat === ItemEffect.Used) {
                    if (!item.infinite && item.use !== Usage.equip) {
                        user.loseItem(itemId)
                    }
                    continue main;
                }
                else if (stat === ItemEffect.Fail) {
                    await user.dlgMsg(content, '使用失败')
                    content = `使用失败: ${content}`
                    continue main;
                } else {
                    content = `"${item.name}"没起任何作用`
                    continue main;
                }
            }
        }
    }
}

function enemyToPlayerDamage(user, atk) {
    if (visitCnt < 3) {
        return 2//dmg
    }
    return Math.max(2, waveAtk + atk - (user.def || 0))
}

function hurtPlayer(other, dmg) {
    const protector = other._protector
    if (protector && protector.hp > 0 && !protector.destroyed && other.position.distance(protector.position) <= W_PROTECT_RADIUS) {
        protector.hurt(dmg * W_PARRY)
        protector.contrib += dmg * W_HURT_CONTRIB
        addProtectIcon(other)
        return false
    } else {
        delProtectIcon(other)
        if (other.isPlayer) {
            if (other.data.role === 'W') {
                if (other.player.action0Button) {
                    dmg *= W_DEF
                }
            }
        }
        other.hurt(dmg)
        return true
    }
}

async function alienShip(ship, lv, rot = PI4, isElite) {
    if (isElite == 1) {
        ship.hp = ship.maxHp = 500 + lv * 200
    } else {
        ship.hp = ship.maxHp = 300 + lv * 100
    }
    ship.collides = true
    ship.fixed = true
    ship.isUfo = true
    ship.isEnemy = true
    ship.meshEmissive = 0.01
    let speed = 1 + Math.random() * 0.2
    // if (Math.random() > 0.5) {
    //     rot *= -1
    // }
    const enemy = ENEMY[ship._mesh]
    const PI = enemy.rot
    ship.meshOrientation = Quat.rotateY(rot + PI)
    const pos = ship.position
    while (!ship.destroyed) {
        const dist = (ship.y - pos.y)
        pos.y += dist * 0.12
        if (Math.abs(dist) < 2) {
            pos.y = ship.y
            ship.velocity.y = 0
            break
        }
        await sleep(80)
    }
    ship.enableDamage = true


    let i = 0
    while (!ship.destroyed && ship.hp > 0) {
        await sleep(80)
        const xs = Math.cos(rot) * speed
        const zs = Math.sin(rot) * speed
        ship.velocity.x = xs
        ship.velocity.z = zs
        ship.meshOrientation = Quat.rotateY(rot + PI)
        // rot += 0.04
        if (pos.x > maxBorder) {
            pos.x -= maplen + 20 * Math.random()
            rot += PI4 * Math.random()
            if (!isBattling) { return ship.destroy() }
        } else if (pos.x < minBorder) {
            pos.x += maplen + 20 * Math.random()
            rot += PI4 * Math.random()
            if (!isBattling) { return ship.destroy() }
        }
        if (pos.z > maxBorder) {
            pos.z -= maplen
            if (!isBattling) { return ship.destroy() }
        } else if (pos.z < minBorder) {
            pos.z += maplen
            if (!isBattling) { return ship.destroy() }
        }

        if (!isBattling) { speed += 0.1 }

        if (i % 4 === 0) {
            const rnd = Math.random() * Difficulty
            if (pos.x < 1 || pos.x > 253 || pos.z < 1 || pos.z > 253) {
            }
            else {
                const prey = Prey.pickOne()

                if (rnd < AlienDroneRate) {
                    if (Math.random() < 0.8) {
                        enemyDropDrone(ship.position, isElite)

                    } else {
                        enemyDropDrone(ship.position, 2)

                    }
                }
                let l = world.querySelectorAll('player,.defense,.turret').find(azzz => pmDist(azzz.position, ship.position) < 20) ? 1 : 0
                if (rnd < AlienBombRate && l == 1) {
                    let bb = enemyDropBomb(ship);
                    if (prey) {
                        let dir = prey.position.sub(ship.position)
                        dir.y /= 2;
                        dir = dir.scale(1 / dir.mag());
                        bb.velocity.copy(dir.scale(Math.min(2, Math.sqrt(pmDist(prey.position, ship.position)))));
                    }
                }
                if (rnd < AlienBombRate && l == 0) {

                    if (prey) {
                        prey.hp > 0 && enemyMissile(ship, prey, '大号的导弹', 1, 'eBullet', 15)

                    }
                }
                if (rnd < AlienGunRate) {
                    prey && enemyShoot(ship, prey, 7, 'beam11', 10)
                }
            }
        }
        i++
    }
    ship.fixed = false
    ship.gravity = true
    ship.collides = true

    if (ship._attacker) {
        ship._attacker._killAcc += 1
    }

    ship.onVoxelContact(({ x, y, z }) => {
        if (!ship.kill) {
            sphereExplode(0, x, y, z, enemy.size)
        }
        ship.kill = true
    })
    ship.onEntityContact(({ other }) => {
        if (!ship.kill) {
            const dmg = enemyToPlayerDamage(other, 55)
            hurtPlayer(other, dmg)
        }
        ship.kill = true
    })
    const v = ship.velocity
    const fall = enemy.rotFall
    ship.meshEmissive = 1
    ship.glowRed()
    ship.particle()
    // ship.velocity.y += Math.random() * 2
    ship.addTag('dead')
    while (!ship.kill) {
        const rotx = Math.atan2(v.y, speed)
        ship.meshOrientation = fall(rotx, PI, rot)
        await sleep(70)
    }
    ship.hp = 1
    ship.hurt(100)
    await sleep(100)
    ship.destroy()
}

const Center = new GameVector3(128, 128, 128)
async function bossShip(ship, lv, rot = PI4) {
    if (visitCnt < 3) {
        ship.hp = ship.maxHp = 3
    } else {
        ship.hp = ship.maxHp = 400000 + lv * 1000
    }
    // ship.hp = ship.maxHp = 1//test
    ship.collides = true
    ship.fixed = true
    ship.isUfo = true
    ship.isEnemy = true
    ship.meshEmissive = 0.01
    ship.guard = true
    ship.deployer = 'boss'
    ship.mass = 100

    let speed = 0.5
    // if (Math.random() > 0.5) {
    //     rot *= -1
    // }
    const enemy = ENEMY[ship._mesh]
    const PI = enemy.rot
    ship.meshOrientation = Quat.rotateY(rot + PI)
    const pos = ship.position
    while (!ship.destroyed) {
        const dist = (ship.y - pos.y)
        pos.y += dist * 0.12
        if (Math.abs(dist) < 2) {
            pos.y = ship.y
            ship.velocity.y = 0
            break
        }
        await sleep(80)
    }
    ship.enableDamage = true

    let i = 0
    while (!ship.destroyed) {
        if (ship.hp <= 0) {
            ship.fixed = false
            ship.gravity = true
            break
        } else {
            if (ship.position.distance(Center) > 60) {
                rot = rotateTo(ship.position, Center, rot, 0.04)
            }
        }
        const xs = Math.cos(rot) * speed
        const zs = Math.sin(rot) * speed
        ship.velocity.x = xs
        ship.velocity.z = zs
        ship.meshOrientation = Quat.rotateY(rot + PI)
        // rot += 0.04
        if (pos.x > maxBorder) {
            pos.x -= maplen + 20 * Math.random()
            rot += PI4 * Math.random()
            if (!isBattling) { return ship.destroy() }
        } else if (pos.x < minBorder) {
            pos.x += maplen + 20 * Math.random()
            rot += PI4 * Math.random()
            if (!isBattling) { return ship.destroy() }
        }
        if (pos.z > maxBorder) {
            pos.z -= maplen
            if (!isBattling) { return ship.destroy() }
        } else if (pos.z < minBorder) {
            pos.z += maplen
            if (!isBattling) { return ship.destroy() }
        }

        if (!isBattling) { speed += 0.1 }

        if (i % 1 === 0) {
            const rnd = Math.random() * Difficulty
            if (pos.x < 1 || pos.x > 253 || pos.z < 1 || pos.z > 253) {
            }
            else if (rnd < 0.5) {
                const prey = Prey.pickOne()
                prey && enemyShoot(ship, prey, 6, '红光球', 20)
                if (rnd < 0.03) {
                    prey && prey.hp > 0 && enemyMissile(ship, prey, '大号的导弹', 1, 'eBullet', 80)
                }
            }
        }
        await sleep(80)
        i++
    }
    // ship.collides = true

    // if (ship._attacker) {
    //     ship._attacker._killAcc += 1
    // }

    /////////////////// ground mode

    if (visitCnt < 3) {
        ship.maxHp = 3
    } else {
        ship.maxHp = 100000
    }
    waveShips += 48;
    ship.hp = ship.maxHp
    ship.removeTag('ship')
    ship.addTag('eDrone')
    ship.mesh = 'mesh/路西法犀牛.vb'
    ship.selfDestruct = async () => {
        ship.glowRed()
        ship.particle()
        ship.done = true
        sphereExplode(0, ship.position.x, ship.position.y, ship.position.z, 8)
        await sleep(1000)
        ship.destroy()
    }
    ship.friction = 0

    ship.onVoxelContact(({ x, y, z, voxel }) => {
        if (ship.done) return
        if (y > 2) {
            // sphereExplode(0, x, y, z, 3)
            voxels.setVoxelId(x, y, z, 0)
        } else {
            voxels.setVoxelId(x, y, z, lava())
        }
        if (y < 2) {
            ship.velocity.y = 0.9
            speed = 0.7
        }
    })

    ship.onEntityContact(({ other, axis }) => {
        if (!ship.done) {
            if (axis.y === 1) {
                hurtPlayer(other, other.maxHp)
            } else {
                const dmg = enemyToPlayerDamage(other, 45)
                hurtPlayer(other, dmg)
                other.velocity.addEq(axis.scale(-3))
            }
        }
    })

    let chaseTarget
    let shootTarget

    const CHASE = 0
    const SHOOT = 1
    const SHOOT_MOVE = 2
    let mode = 0
    while (!ship.destroyed && !ship.done) {
        if (ship.hp <= 0) {
            ship.selfDestruct()
            const { x, y, z } = ship.position
            sphereExplode(0, x | 0, y | 0, z | 0, 7)
            ship.velocity.set(0, 0, 0)
            break
        }
        if (i % 20 === 0) {
            const tgList = ship.nearestAliveEntities(Prey)
            chaseTarget = tgList[0]
            shootTarget = tgList.slice(-3).pickOne()
        }
        if (i % 120 === 0) {
            ship.velocity.addEq(new GameVector3(random(-1, 1), 0.5, random(-1, 1)))
            const rnd = Math.random()
            if (rnd < 0.3) {
                mode = CHASE
            }
            else if (rnd < 0.8) {
                mode = SHOOT_MOVE
            }
            else {
                mode = SHOOT
            }
            // log('mode')
        }

        if (mode === CHASE) {
            speed = 1.5
        } else if (mode === SHOOT) {
            speed = 0.005
        } else if (mode === SHOOT_MOVE) {
            speed = 0.4
        }

        if (shootTarget) {
            if (mode === SHOOT_MOVE) {
                if (i % 10 == 0) {
                    shootTarget.hp > 0 && enemyShoot(ship, shootTarget, 6, '红光球', 10, 2)
                }
            }
            else if (mode === SHOOT) {
                if (i % 2 === 0) {
                    shootTarget.hp > 0 && enemyShoot(ship, shootTarget, 6, '红光球', 5, 2)
                    if (i % 15 == 0 && Math.random() < 0.5) {
                        shootTarget.hp > 0 && enemyMissile(ship, shootTarget, '大号的导弹', 3, 'eBullet', 10)
                    }
                }
                chaseTarget = shootTarget
            }
        }

        if (chaseTarget) {

            let dx = chaseTarget.position.x - ship.position.x
            let dz = chaseTarget.position.z - ship.position.z
            // let dy = target.position.y - e.position.y
            let dist = Math.sqrt(dx * dx + dz * dz) / speed
            dx /= dist
            dz /= dist
            ship.meshOrientation = Quat90.rotateY(Math.atan2(dz, dx))
            if (dist > 0.5) {
                ship.velocity.set(dx, ship.velocity.y, dz)
            } else {
                ship.velocity.set(random(-0.5, 0.5), 0.1, random(-0.5, 0.5))
            }
        } else {
            ship.velocity.set(0, 0, 0)
        }
        if (ship.position.y < -5) {
            ship.position.set(128, 78, 128)
        } else if (ship.position.y > 150) {
            ship.position.set(128, 48, 128)
        }
        await sleep(70)
        i++
    }

    // const v = ship.velocity
    // const fall = enemy.rotFall
    // ship.meshEmissive = 1
    // ship.glowRed()
    // ship.particle()
    // // ship.velocity.y += Math.random() * 2
    // ship.addTag('dead')
    // while (!ship.kill) {
    //     const rotx = Math.atan2(v.y, speed)
    //     ship.meshOrientation = fall(rotx, PI, rot)
    //     await sleep(70)
    // }
    // ship.hp = 1
    // ship.hurt(100)
    // await sleep(100)
    // ship.destroy()
}

/**
 * @param bunny {GameEntity}
 */
function bunnyJump(bunny, oldPos, state) {
    // bunny.velocity.y = 2.1
    if (bunny.voxelContacts.length && state.jump <= 0 && Math.random() < 0.2) {
        state.jump = 2//+Math.round(Math.random()*2)
        const pos = bunny.position
        const dist = pos.distance(oldPos)
        if (dist > 8) {
            bunny.position.copy(oldPos)
            bunny.velocity.set(0, 0, 0)
            bunny._reset()
        } else if (dist > 5) {
            const xs = oldPos.x - pos.x
            const zs = oldPos.z - pos.z
            const rot = Math.atan2(zs, xs)
            state.rot = rot
            bunny.velocity.y = 1
            bunny.velocity.x = xs * 0.1
            bunny.velocity.z = zs * 0.1
            bunny.meshOrientation = Quat.rotateY(rot)
        } else {
            const speed = (0.1 + Math.random() * 0.1)
            const xs = Math.cos(state.rot) * speed
            const zs = Math.sin(state.rot) * speed
            bunny.velocity.y = 0.8 + 0.3 * Math.random()
            bunny.velocity.x = xs
            bunny.velocity.z = zs
            bunny.meshOrientation = Quat.rotateY(state.rot)
        }
    } else if (state.jump-- > 0) {
    } else {
        state.rot += Math.PI * (Math.random() - 0.5) * 0.5
        bunny.meshOrientation = Quat.rotateY(state.rot)
    }
}

GameEntity.prototype.hasItem = function (itemId) {
    return this.data.item.includes(itemId)
}

GameEntity.prototype.gainExp = function (exp) {
    const prev = this.lv()
    this.data.exp += exp
    if (this.data.exp > MAX_EXP) {
        this.data.exp = MAX_EXP
        return '到达最大等级限制'
    }
    const now = this.lv()
    updatePlayer(this, 'exp')
    if (now > prev) {
        calcUserProp(this)
        return `获得${exp}经验, 升级到Lv${now}!`
    } else {
        return `获得${exp}经验`
    }
}

GameEntity.prototype.dropItem = async function (list, rate) {
    const rnd = Math.random()
    if (rnd < rate + this.luck) {
        const itemId = list.pickByWeight()
        if (isRare(itemId) && this.hasItem(itemId)) {
            return
        }
        await gainItem(this, itemId, true)
    }
}

GameEntity.prototype.gainCoin = async function (coin, dlg) {
    const user = this
    let text
    if (user.data.coin >= MAX_COIN) {
        user.data.coin = MAX_COIN
        text = `获得了${coin}${CURRENCY}, 已达${MAX_COIN}上限, 无法携带更多${CURRENCY}`
    } else {
        user.data.coin += coin
        text = `获得了${coin}${CURRENCY}`
    }
    updatePlayer(user, 'coin')
    dlg ? await user.dlgMsg(text, `${CURRENCY}入手`) : user.msg(text)
}
GameEntity.prototype.gainCoinExp = async function (coin, exp, dlg, prefixText = '') {
    const user = this
    let coinText
    let expText
    if (user.data.coin >= MAX_COIN) {
        user.data.coin = MAX_COIN
        coinText = `获得了${coin}${CURRENCY}, 已达${MAX_COIN}上限, 无法携带更多${CURRENCY}`
    } else {
        user.data.coin += coin
        coinText = `获得了${coin}${CURRENCY}`
    }

    const prev = this.lv()
    this.data.exp += exp
    if (this.data.exp > MAX_EXP) {
        this.data.exp = MAX_EXP
        expText = '你已经达到最大等级限制'
    }
    const now = this.lv()

    if (now > prev) {
        calcUserProp(this)
        expText = `获得${exp}经验, 升级到Lv${now}!`
    } else {
        expText = expText || `获得${exp}经验`
    }

    await updatePlayer(user, 'coin', 'exp')
    const allText = prefixText + coinText + '\n\n' + expText
    dlg ? await user.dlgMsg(allText, `结算成功`) : user.msg(allText)
}

GameEntity.prototype.gainEduScore = async function (edu_score, dlg) {
    const user = this
    let text
    if (user.data.edu_score >= MAX_EDU) {
        user.data.edu_score = MAX_EDU
        text = `获得了${edu_score}\0学分, 已达${MAX_EDU}上限, 无法累积更多\0学分`
    } else {
        user.data.edu_score += edu_score
        text = `获得了${edu_score}\0学分`
    }
    await updatePlayer(user, 'edu_score')
    dlg ? await user.dlgMsg(text, `\0学分入手`) : user.msg(text)
}

GameEntity.prototype.consumeEduScore = async function (edu_score, itemId, dlg) {
    const user = this
    let text
    if (user.exceedCapacity()) {
        text = `背包已满, 无法入手${ItemDict[itemId].name}`
    }
    else if (user.data.edu_score < edu_score) {
        text = `需要${edu_score}\0学分, 你只有${this.data.edu_score}\0学分, 还差${edu_score - this.data.edu_score}`
    }
    else {
        user.data.edu_score -= edu_score
        user.data.item.unshift(itemId)
        await updatePlayer(user, 'edu_score', 'item')
        text = `${edu_score}\0学分换取"${ItemDict[itemId].name}"`
    }
    dlg ? await user.dlgMsg(text, '\0学分消耗') : user.msg(text)
}

GameEntity.prototype.exceedCapacity = function (quantity = 1) {
    return this.data.item.length + quantity > this.capacity
}

GameEntity.prototype.discardItem = async function (itemId) {
    const item = ItemDict[itemId]
    if (this.data.wear[item.type] === itemId) {
        unequip(this, itemId)
    }
    this.loseItem(itemId)
}

GameEntity.prototype.loseItem = async function (itemId) {
    this.data.item.removeElem(itemId)
    await updatePlayer(this, 'item')
}

async function buyItem(buyer, itemId, coin, dlg, quantity = 1) {
    const item = ItemDict[itemId]
    let text
    let succ = false
    coin *= quantity
    let item_name = item.name + 'x' + quantity
    if (buyer.data.coin < coin) {
        text = `"${item_name}"需要${coin}${CURRENCY}, 你只有${buyer.data.coin}, 还差${coin - buyer.data.coin}${CURRENCY}`
    }
    else if (buyer.exceedCapacity(quantity)) {
        text = `道具已满${buyer.data.item.length}件, 没法收纳"${item_name}"`
    }
    else if (!canBuy(buyer, itemId)) {
        text = `"${buyer.roleName()}"不能买"${item.name}"`
    }
    else {
        const items = Array(quantity).fill(itemId)
        buyer.data.item.unshift(...items)
        buyer.data.coin -= coin
        text = `付出了${coin}${CURRENCY}, 买到了"${item_name}"`
        await updatePlayer(buyer, 'item', 'coin')
        succ = true
    }
    log(succ)
    dlg ? await buyer.dlgMsg(text, succ ? '购买成功' : '购买失败') : buyer.msg(text)

    return succ
}

async function trade(buyer, seller, itemId, coin) {
    const item = ItemDict[itemId]
    const itemQuant = seller.data.item.countElem(itemId)
    if (itemQuant === 0) {
        const text = `"${item.name}"已被别人抢先买光`
        await buyer.dlgMsg(text)
        return text
    }
    else if (itemQuant === 1 && Object.values(seller.data.wear).includes(itemId)) {
        const text = `"${item.name}"被 ${seller.name()}留着作为自己的装备`
        await buyer.dlgMsg(text)
        return text
    }
    else if (buyer.data.coin < coin) {
        const text = `购买"${item.name}"需要${coin}${CURRENCY}, 你只有${buyer.data.coin}, 还差${coin - buyer.data.coin}${CURRENCY}`
        buyer.msg(text)
        return `钱不够买"${item.name}"`
    } else {
        buyer.data.coin -= coin
        seller.data.coin += coin
        seller.data.item.removeElem(itemId)
        const contrib = T_SELL_CONTRIB * coin
        seller.contrib += contrib
        seller.roundContrib += contrib
        seller._sellAcc += 1
        seller._earnAcc += coin
        buyer.data.item.unshift(itemId)
        buyer.msg(`付出了${coin}${CURRENCY}, 获得"${item.name}"`)
        seller.msg(`${buyer.name()}以${coin}${CURRENCY}买走"${item.name}"`)
        updatePlayer(buyer, 'item', 'coin')
        updatePlayer(seller, 'item', 'coin')
        return `成功购买"${item.name}"`
    }
}

GameEntity.prototype.gainItem = async function (itemId, dlg) {
    let text
    const item = ItemDict[itemId]
    if (this.exceedCapacity()) {
        text = `道具已满${this.data.item.length}件, 没法收纳"${item.name}"`
    } else {
        this.data.item.unshift(itemId)
        text = `获得了 "${item.name}"`
        await updatePlayer(this, 'item')
    }
    dlg ? await this.dlgMsg(text, '道具入手') : this.msg(text)
}

GameEntity.prototype.gainItemListAndCoin = function (str, itemList, coin) {
    let text = [str]
    this.data.coin += coin
    text.push(`${coin}${CURRENCY}`)
    if (this.data.coin >= MAX_COIN) {
        this.data.coin = MAX_COIN
    }

    this.data.item.unshift(...itemList)
    for (const id of itemList) {
        text.push(ItemDict[id].name)
    }

    text = text.join('\n')
    // await updatePlayer(this, 'item', 'coin')
    return text
}

GameEntity.prototype.loseCoin = async function (coin, dlg, desc) {
    if (this.data.coin < coin) {
        const text = `${desc || ''}需要${coin}${CURRENCY}, 你只有${this.data.coin}, 还差${coin - this.data.coin}${CURRENCY}`
        dlg ? await this.dlgMsg(text, `${CURRENCY}不够`) : this.msg(text)
        return false
    } else {
        this.data.coin -= coin
        const text = `付出了${coin}${CURRENCY}` + (desc ? `, ${desc}` : '')
        await updatePlayer(this, 'coin')
        dlg ? await this.dlgMsg(text, `交易成功`) : this.msg(text)
        return true
    }
}

async function updatePlayer(user, ...keyList) {
    if (user._emergency) return
    if (user.isGuest()) return
    // const data = user.data
    // const valList = keyList.map(e => {
    //     const v = data[e]
    //     if (v.constructor === Array || v.constructor === Object) {
    //         return JSON.stringify(v)
    //     }
    //     return v
    // })
    // let head = keyList.shift()
    // let tail = keyList.map((e, i) => `,${e}=`)
    // const cmd = [`UPDATE player SET ${head}=`, ...tail, ` WHERE userkey=`, ';']
    // return await poll(() => db.sql(cmd, ...valList, user.player.userKey))
    if (!user.lastSave || new Date().getTime() - user.lastSave > 30000) {
        savePlayer(user)
    }
    return;
}

// async function loadPlayer(user, list) {
//     const userKey = user.player.userKey
//     const data = (await poll(() => db.sql`SELECT * FROM player WHERE userkey=${userKey};`))[0]
//     // world.say(json(data))
//     if (data) {
//         for (const k of list) {
//             data[k] = JSON.parse(data[k])
//         }
//         user.data = data
//     }
// }



function isDoor(npc) {
    return npc.id.endsWith('出口')
}

function itemName(id) {
    return ItemDict[id].name
}

Array.prototype.runMatch = function patternMatch(pattern) {
    const found = this.find((e) => e[0].startsWith(pattern))
    if (found) {
        return found[1]()
    }
    return false
}

Array.prototype.count = function () {
    const obj = {}
    for (const e of this) {
        if (obj[e]) {
            obj[e]++
        } else {
            obj[e] = 1
        }
    }
    return obj
}

function mapPairs(obj, fn) {
    const out = {}
    for (var k in obj) {
        out[k] = fn(obj[k])
    }
    return out
}

Array.prototype.groupBy = function (prop) {
    const obj = {}
    for (const e of this) {
        const v = e[prop]
        if (obj[v]) {
            obj[v].push(e)
        } else {
            obj[v] = [e]
        }
    }
    return obj
}

Array.prototype.countElem = function (elem) {
    return this.filter(e => e === elem).length
}

Array.prototype.removeElem = function (e) {
    const idx = this.findIndex(ee => ee === e)
    this.splice(idx, 1)
    return this
}

Array.prototype.isLastElem = function (elem) {
    return elem === this[this.length - 1]
}
Array.prototype.shuffle = function shuffle(size) {
    var array = this
    var index = -1,
        length = array.length,
        lastIndex = length - 1;
    size = size === undefined ? length : size;
    while (++index < size) {
        // var rand = baseRandom(index, lastIndex),
        var rand = index + Math.floor(Math.random() * (lastIndex - index + 1))
        value = array[rand];
        array[rand] = array[index];
        array[index] = value;
    }
    array.length = size;
    return array;
}

Array.prototype.pickOne = function () {
    return this[(Math.random() * this.length) | 0]
}

Array.prototype.pickByWeight = function pickByWeight() {
    var array = this
    let acc = 0
    for (const [i, w] of array) {
        acc += w
    }
    let rnd = Math.random() * acc
    acc = 0
    for (const [i, w] of array) {
        acc += w
        if (rnd < acc) {
            return i
        }
    }
}

GameQuaternion.prototype.toEuler = function quatToEuler() {
    const q = this
    const qw = q.w, qx = q.x, qy = q.y, qz = q.z
    // const qx=q.w, qy=q.x, qz=q.y, qw=q.z
    // const qz=q.w, qw=q.x, qx=q.y, qy=q.z
    const xx = Math.atan2(-2 * (qy * qz - qw * qx), qw * qw - qx * qx - qy * qy + qz * qz);
    let yy = Math.asin(2 * (qx * qz + qw * qy));
    const zz = Math.atan2(-2 * (qx * qy - qw * qz), qw * qw + qx * qx - qy * qy - qz * qz);

    if (Math.abs(qx) < 0.704) {
        yy = Math.PI - yy
    }

    return new GameVector3(xx, yy, zz)
}

function isBusyHour() {
    if (world.querySelectorAll('player').length > WILD_USER_NUM) {
        return true
    }
    return false
    // return true
}

GameEntity.prototype.dlgLook = async function (content, title, lookEye, lookTarget) {
    this._interact++
    const sel = await this.player.dialog({
        type: GameDialogType.TEXT,
        title,
        content,
        lookEye,
        lookTarget: lookTarget.constructor === String ? world.querySelector('#' + lookTarget).position : lookTarget,
    })
    this._interact--
    return sel
}

GameEntity.prototype.dlgMsg = async function (content, npc_name, hasArrow) {
    this._interact++
    const sel = await this.player.dialog({
        type: GameDialogType.TEXT,
        title: npc_name,
        content,
        hasArrow,
    })
    this._interact--
    return sel
}

GameEntity.prototype.dlgInput = async function (content, npc_name) {
    this._interact++
    const sel = await this.player.dialog({
        type: GameDialogType.INPUT,
        title: npc_name,
        content,
    })
    this._interact--
    return sel
}

GameEntity.prototype.dlgSel = async function (content, title, options, mesh) {
    this._interact++
    if (mesh) {
        mesh = addWear(this, mesh)
    }
    this.lastActive = Date.now()
    const sel = await this.player.dialog({
        type: GameDialogType.SELECT,
        title,
        content,
        options,
    })
    if (mesh) {
        this.player.removeWearable(mesh)
    }
    this._interact--

    // const now = Date.now()
    // if (now - this._lastSelMS < 400) {
    //     return ''
    // }
    // this._lastSelMS = now

    return sel || ''
}

GameEntity.prototype.menu = async function menu(content, title, fnDict, disp = {}) {
    this._interact++
    const list = Object.keys(fnDict)
    const options = list.map(e => disp[e] ? disp[e]() : e)
    const sel = await this.player.dialog({
        type: GameDialogType.SELECT,
        title,
        content,
        options,
    })
    this._interact--
    if (sel) {
        const fn = fnDict[list[sel.index]]
        if (fn) {
            await fn()
            return true
        }
    }
    return false
}

GameEntity.prototype.menuLoop = async function menuLoop(fn) {
    while (this.ready) {
        if (!(await this.menu(...fn()))) {
            break
        }
    }
}

GameEntity.prototype.msg = function msg(text) {
    this.player.directMessage(text)
}

GameEntity.prototype.npcYell = async function (npc, fn, n) {
    while (!this.destroyed) {
        await sleep(n * (1 + Math.random() * 0.5))
        npc.say(fn())
    }
}

GameEntity.prototype.jobNPC = function (data) {
    this._mem = {}
    this.isNPC = true
    this.enableInteract = true
    this.enableDamage = true
    this.interactColor = GreenColor
    this.interactHint = data.name || this.id
    this.interactRadius = data.radius || 6
    this.safeInteract(async ({ entity: user }) => {
        data.idle || this.faceTo(user)
        if (data.run) {
            data.run(user, this)
        } else {
            npcDlg(user, this, data)
        }
        // world.onTick(({tick})=>{
        //     faceTo(npc,user)
        // })
    })
    if (data.yell) {
        npcYell(this, data.yell, YELL_MS)
    }
    if (data.mark) {
        if (data.mark(this, data)) {
            const mk = spawn('感叹号', this.position)
            mk.position.y += 4
        }
    }
    data.init && data.init(this)
}

const Quat = new GameQuaternion(0, 0, 0, 1).rotateY(Math.PI / 2)
GameEntity.prototype.faceTo = function (user, rot = 0) {
    // var direction = user.position.sub(this.position)
    const dz = user.position.z - this.position.z
    const dx = user.position.x - this.position.x
    this.meshOrientation.copy(Quat.rotateY(Math.atan2(dz, dx) + rot))
}

GameEntity.prototype.rotZfaceTo = function (user, rotz, rot = 0) {
    // var direction = user.position.sub(this.position)
    const dz = user.position.z - this.position.z
    const dx = user.position.x - this.position.x
    const tg = Math.atan2(dz, dx)

    if (this.rotation > Math.PI) {
        this.rotation -= PIx2
    }
    else if (this.rotation < -Math.PI) {
        this.rotation += PIx2
    }

    let diff = tg - this.rotation

    if (diff < -Math.PI) {
        diff += PIx2
    }
    else if (diff > Math.PI) {
        diff -= PIx2
    }

    this.rotation += diff * 0.5

    this.meshOrientation.copy(Quat.rotateZ(rotz).rotateY(this.rotation + rot))
}

GameEntity.prototype.teleToId = function (id) {
    this.teleportTo(world.querySelector('#' + id), true)
}

GameEntity.prototype.teleportTo = function (npc, inv) {
    const user = this
    const euler = npc.meshOrientation.toEuler()
    let angle = euler.y

    if (inv) {
        angle += Math.PI
    }

    const dist = 1.5
    let x = Math.cos(angle) * dist
    const z = Math.sin(angle) * dist

    if (npc.destroyed) {
        user.position.copy(user.player.spawnPoint)
    } else {
        user.position.copy(npc.position)

        user.position.x += x
        user.position.z += z
        user.position.y += Math.random()
    }
}

const cancelRevChar = String.fromCharCode(30)
GameEntity.prototype.name = function name() {
    return this.player.name + cancelRevChar
}

GameEntity.prototype.roleName = function () {
    return Role[this.role].name
}

GameEntity.prototype.lv = function lv() {
    return Math.pow(this.data.exp + 1, 0.25) | 0
}

GameEntity.prototype.nextLvExp = function lv() {
    const next = lvExp(this.lv() + 1)
    return next - this.data.exp
}

function lvExp(lv) {
    return Math.pow(lv, 4) - 1
}

GameEntity.prototype.safeInteract = function (fn) {//防止多次触发interact打开多个相同对话框
    return this.onInteract(async (evt) => {
        const user = evt.entity
        if (!user.ready) return user.msg(`玩家数据暂时未成功初始化`)
        if (user.player.invisible || user.hp <= 0) return;
        if (user._interact <= 0) {
            user._altMode = user.player.crouchButton
            user._interact++
            await fn(evt)
            user._interact--
        }
    })
}

GameEntity.prototype.makeGift = function makeGift(itemId) {
    const gift = spawn('礼盒', this.position)
    if (!gift) return;
    gift.position.y += 2
    gift.addTag('gift')
    gift.collides = true
    gift.gravity = true
    gift.interactHint = itemName(itemId)
    gift.enableInteract = true
    gift.interactRadius = 1.7
    gift.friction = 0.3
    const scale = gift.meshScale.x * 0.5
    gift.meshScale.set(scale, scale, scale)
    gift.safeInteract(async ({ entity: user }) => {
        gift.destroy()
        await gainItem(user, itemId)
    })
    gift.onFluidEnter(() => {
        gift.destroy()
    })
}

const { quizList } = require('./quiz.js')
async function quiz(user) {
    const list = quizList.shuffle().slice(0, EDU_NUM)
    let i = 1
    let score = 0
    while (list.length) {
        let [q, a, b, c] = list.pop()

        q = `${q}?`

        const ans = [
            [a, true],
            [b, false],
        ]

        if (c) {
            ans.push([c, false])
        }

        ans.shuffle()

        const sel = await user.dlgSel(q, `进度: ${i++}/${EDU_NUM} 答对${score}道`, ans.map(e => e[0]))
        if (sel) {
            const pick = ans[sel.index]
            if (pick[1]) {
                user.msg('答对了')
                score++
            } else {
                user.msg('答错了')
            }
        } else {
            return await user.dlgMsg(`放弃考试`)
        }
    }

    if (score >= EDU_THRES) {
        user.gainEduScore(score)
        await user.dlgMsg(`恭喜你通过这次考试, 获得${score}\0学分\n当前\0学分总计${user.data.edu_score}`)
    } else {
        await user.dlgMsg(`答对的题目低于${EDU_THRES}道, 抱歉这次考试没能通过~`)
    }
}

var boss_edu_score = 0
async function bossQuiz(user) {
    const list = quizList.shuffle().slice(0, EDU_NUM)
    let i = 1
    let score = 0
    while (list.length) {
        let [q, a, b, c] = list.pop()

        q = `${q}?`

        const ans = [
            [a, true],
            [b, false],
        ]

        if (c) {
            ans.push([c, false])
        }

        ans.shuffle()

        const sel = await user.dlgSel(q, `进度: ${i++}/${EDU_NUM} 答对${score}道`, ans.map(e => e[0]))
        if (sel) {
            const pick = ans[sel.index]
            if (pick[1]) {
                user.msg(`答对了, 破解进度: ${boss_edu_score}/${BOSS_EDU_NUM}`)
                boss_edu_score++
                score++
                user.contrib += 20
                if (bigBoss.guard) {
                    if (boss_edu_score >= BOSS_EDU_NUM) {
                        bigBoss.guard = false
                        world.say(`${user.name()}成功破解${BossName}的装甲防护系统`)
                        return
                    }
                } else {
                    return user.dlgMsg(`${BossName}装甲已被破解`)
                }
            } else {
                user.msg('答错了')
            }
        } else {
            return await user.dlgMsg(`请继续努力`)
        }
    }

    await user.dlgMsg(`先休息一下, 回来继续破解`)
    world.say(`${BossName}装甲破解进度: ${boss_edu_score}/${BOSS_EDU_NUM}`)
}
//adjustWearable('suitT1',{offset:[0, 0, 0],rot:[0,0,0],scale:1,part:'head'})
global.adjustWearable = (itemId, data) => {
    const item = ItemDict[itemId]
    WEARABLES[item.mesh] = data
    const part = data.part || GameBodyPart.RIGHT_HAND
    const user = world.querySelector('player')
    const wear = user.player.wearables(part)
    user.player.removeWearable(wear[0])
    equip(user, itemId)
    showWearable()
}
global.showWearable = () => {
    world.say(JSON.stringify(WEARABLES).replace(new RegExp('},', 'g'), '},\n'))
    // console.log(JSON.stringify(WEARABLES).replace(new RegExp('},','g'),'},\n'))
}

function addWear(user, mesh) {
    const weardata = WEARABLES[mesh]
    const rot = weardata.rot
    let scale = weardata.scale
    if (scale) {
        if (scale.constructor === Number) {
            scale = [scale, scale, scale]
        }
    } else {
        scale = [0.5, 0.5, 0.5]
    }
    const part = weardata.part || GameBodyPart.RIGHT_HAND
    const wear = {
        bodyPart: part,
        mesh: meshPath(mesh),
        orientation: rot ? Quat
            .rotateZ(rot[2] && Math.PI / rot[2])
            .rotateX(rot[0] && Math.PI / rot[0])
            .rotateY(rot[1] && Math.PI / rot[1]) : Quat.rotateZ(-Math.PI * 0.5).rotateX(Math.PI),
        scale,
        offset: weardata.offset,
    }
    return user.player.addWearable(wear)
}
function equip(user, itemId) {
    const item = ItemDict[itemId]
    const mesh = item.mesh
    const wear = addWear(user, mesh)
    user._wear[item.type] = wear
}

function unequip(user, itemId, nosave) {
    unequipNoUpdate(user, itemId)
    calcUserProp(user)
    nosave || updatePlayer(user, 'wear')
}

function unequipNoUpdate(user, itemId) {
    // console.log(itemId)
    const item = ItemDict[itemId]
    user.player.removeWearable(user._wear[item.type])
    delete user._wear[item.type]
    delete user.data.wear[item.type]
}

function hasEquip(user, itemId) {
    const item = ItemDict[itemId]
    return user._wear[item.type]
}

function wearEquip(user, itemId) {
    const item = ItemDict[itemId]
    if (item.mesh) {
        const oldItemID = user.data.wear[item.type]
        if (oldItemID) {
            unequip(user, oldItemID, true)
        }
        equip(user, itemId)
    }
    user.data.wear[item.type] = itemId
    calcUserProp(user)
    updatePlayer(user, 'wear')
}

function isNullSelect(selection) {
    return !selection || !selection.value
}

function roleStat(user) {
    if (user.role === 'S') {
        return `(伤敌${user._dmgAcc}hp/击坠${user._killAcc})`
    }
    if (user.role === 'M') {
        return `(治${user._healAcc}hp/救${user._reviveAcc})`
    }
    if (user.role === 'E') {
        return `(修${user._fixAcc}格/部署${user._deployAcc}${CURRENCY})`
    }
    if (user.role === 'T') {
        return `(售${user._sellAcc}件/资金${user.data.coin}${CURRENCY})`
    }
    return ''
}

async function takeReward(user, coin, exp) {
    if (user.contrib > 0) {
        const text = `扣除${user.contrib | 0}贡献值\n\n`
        user.contrib = 0
        await user.gainCoinExp(coin, exp, true, text)
    } else {
        // user.msg(`检测到刷分行为`)
        // user.data.task.cheat = (user.data.task.cheat || 0) + 1
        // updatePlayer(user, 'task')
    }
}

world.onPress(async (e) => {

    const { entity: user, button } = e
    if (!user.ready) return user.msg(`玩家数据暂时未成功初始化`)
    if (user.hp <= 0) return
    let sel = `.${user.player.userId},.Deployment,.dun`;
    if (user.data.role == 'E') {
        world.querySelectorAll('.Deployment').forEach((dd) => {
            if (dd.hp >= dd.maxHp || (dd.maxHp > 1000 && Math.random() < 0.5 && dd.hp > 900)) {
                dd.addTag('fullHpDeployment');
            }
        })
        sel = `.${user.player.userId},.fullHpDeployment,.dun`
    }
    e.raycast = world.raycast(e.raycast.origin, e.raycast.direction, { ignoreSelector: sel })

    world.querySelectorAll('.fullHpDeployment').forEach((dd) => {
        dd.removeTag('fullHpDeployment');
    })

    user.lastActive = Date.now()
    if (button == GameButtonType.DOUBLE_JUMP) {
        await sleep(500);
        if (!user.player.jumpButton) return;
        const weaponId = user.data.wear.背包;
        if (weaponId == 'bagFly') {
            (async function () {
                let time = 0;
                user.particleRate = 200;
                user.particleVelocity = new GameVector3(0, -2, 0)
                user.particleColor = new GameRGBColor(1, 1, 1);
                while (user.player.jumpButton) {
                    user.velocity.y = 1.2
                    time++;
                    await sleep(100);
                    if (time > 20) {
                        break;
                    }
                }
                user.velocity.y = -0.5;
                user.player.enableJump = false;
                await sleep(time * 100);
                user.player.enableJump = true;
                user.particleRate = 0;
            })();
        }
    }
    if (button === GameButtonType.ACTION1) {
        if (isAdmin(user)) {
            if (user.player.crouchButton && user.player.jumpButton) {
                // user.dlgSel(
                //     players()
                //         .sort((a, b) => (b.data.task.cheat | 0) - (a.data.task.cheat | 0))
                //         .map(e => `${e.name()}刷分${e.data.task.cheat | 0}次 ${e.data.exp}exp`).join('\n')
                //     , '刷分排行榜'
                // )
                while (true) {
                    const pList = world.querySelectorAll('player').filter(e => e._err)
                    let pick = await user.dlgSel('pick one', 'user error', pList.map(e => e._err.substring(0, 3) + ':' + e.name()))
                    if (pick && pick.value) {
                        pick = pList[pick.index]
                        const msg = `${pick.player.name}\n${pick.player.userKey}\n\n${pick._err}`
                        user.dlgMsg(msg, 'user error')
                    } else {
                        break
                    }
                }

                return
            }
            else if (user.player.crouchButton && user.player.walkButton) {
                const pList = [...Errors]
                user.dlgMsg(pList.join('\n\n'), 'map error')
                return
            }
            else if (user.player.crouchButton && user.player.action0Button) {
                while (true) {
                    const pList = world.querySelectorAll('player').sort((a, b) => b.data.exp - a.data.exp)
                    let pick = await user.dlgSel('', 'bonus', pList.map(e => `Lv${e.lv()} ${e.roleName()} ${e.name()}`))
                    if (pick && pick.value) {
                        pick = pList[pick.index]
                        const msg = `${pick.player.name}\n${pick.player.userKey}\n\n`
                        user.dlgMsg(msg, 'bonus')
                    } else {
                        break
                    }
                }
            }
        }
        if (user._turret) {
            user._turret.leave()
            return
        }

        const p = user.position;

        if (voxels.getVoxelId(p.x | 0, Math.floor(p.y) + 0.5 | 0, p.z | 0) ||
            voxels.getVoxelId(p.x | 0, Math.floor((p.y + 1)) + 0.5 | 0, p.z | 0)) {
            return user.menu('不小心被埋在建筑里?', '施工事故救援服务', {
                async 传送回出生点() {
                    user.player.forceRespawn()
                }
            })
        }

        const data = user.data
        user.menuLoop(() => {

            /*world.onPlayerJoin(async ({ entity }) => {
                while (!entity.destroyed) {
                    remoteChannel.sendClientEvent(entity, {
                        type: 'coinandexp',
                        data: [
                            `${coin}`,
                            `${nextLvExp()}`
                        ]
                    })

                    await sleep(0.5 * 1000)

                }
            })*/

            const atk = Role[user.role].atk(user)
            const role = Role[user.role]
            const text = `${user.isGuest() ? ' (游客数据不会被保存哦~)\n' : ''}称号: ${user.interactHint}
下次升级还需 ${user.nextLvExp()}经验
${CURRENCY}币:${data.coin}
生命:(${Math.ceil(user.hp)}/${user.maxHp}) ${user.faction ? `派系:${user.faction}` : ''}
能力:(本能${user.atk}/${role.atkName}${atk < 1 ? Math.floor(atk * 100) + '%' : atk}/防御${user.def}${user.radius ? `/射程${user.radius}` : ''})
\0学分:${data.edu_score} ${user.luck ? `运气+${user.luck * 100}%` : ''}
装备:[${Object.values(data.wear).map(e => ItemDict[e].name).join('/') || '无'}]
`.trim()
            return [
                text,
                user.name() + '的状态',
                {
                    async 道具() {
                        await user.itemMenu()
                    },
                    async 职能() {
                        let dict
                        if (user.role === 'N') {
                            dict = {
                                async 瞬移到学校() {
                                    user.teleToId('jiqirennpc-1')
                                },
                            }
                        }
                        else {
                            dict = {}
                        }
                        await user.menu(Role[user.role].desc, `${user.roleName()}职能`, dict)
                    },
                    async 结算() {
                        const salary = calcSalary(user)
                        const exp = calcExpReward(user)
                        let text
                        let options
                        if (user.contrib >= CONTRIB_THRES) {
                            text = `你本轮目前累计${user.contrib | 0}贡献值\n\n工资: +${salary}${CURRENCY}\n经验: +${exp}\n\n(回营房结算有额外补贴)`
                            options = {
                                async 确认结算() {
                                    await takeReward(user, salary, exp)
                                },
                            }
                        } else {
                            text = `你本轮累计贡献值还不到${CONTRIB_THRES}, 无法结算 :)`
                            options = {}
                        }
                        await user.menu(text, '就地结算', options)
                    },
                    async 城市状况() {
                        try {
                            const all = players()

                            const roleCount = all.map(e => e.role).count()
                            for (const role of 'STEM') {
                                roleCount[role] = roleCount[role] || 0
                            }

                            const noStudentCnt = Object.assign({}, roleCount)
                            delete noStudentCnt['N']

                            const minCnt = Object.entries(noStudentCnt).sort(([a, an], [b, bn]) => an - bn)[0][1]
                            let enemyText = waveShips > 0 ? ` 敌剩余增援:${waveShips}架\n\n` : '\n\n'
                            let utilText = `公共设施:${Deployments.length}/${DeployQuota}`
                            const now = Date.now()
                            let text = utilText + enemyText + Object.entries(roleCount).map(([role, n]) => `${Role[role].name}: ${n}人${(n === minCnt && role !== 'N') ? ' (人才紧缺)' : ''}`).join('\n') +
                                `\n\n${all.sort((a, b) => b.data.exp - a.data.exp).map((e, i) => `${e.isActive(now) ? i + 1 : '挂机'}.${e.name()}Lv${e.lv()} ${e.roleName()} ${roleStat(e)}`).join('\n')}`
                            const point = world.createEntity({
                                fixed: true,
                                collides: false,
                                position: user.position,
                                enableInteract: true,
                                interactHint: '你在这里',
                                interactColor: GreenColor,
                                interactRadius: 0.1,
                            })

                            await user.dlgLook(text, '城市状况', birdsEyeHC, birdsEyeLC)

                            point.destroy()
                        } catch (e) {
                            user._err = '@1@' + e.toString()
                            user.dlgMsg(user._err)
                            world.say(e.stack)
                            logger.log(`[Error] User menu: <${user.player.userId}>${user.player.name}@1@${e}`);
                        }
                    },
                },
                {
                    道具: () => `道具 (${data.item.length}/${user.capacity})`,
                    结算: () => `结算并保存(${user.contrib | 0}贡献值)`,
                    职能: () => `${user.roleName()}职能`
                },
            ]
        })
    } else if (button === GameButtonType.ACTION0) {
        if (user._turret) {
            const tg = e.raycast.hitEntity
            if (tg) {
                if (tg.isUfo) {
                    user._turret.aim(tg)
                    user.msg(`成功锁定敌方飞船, 自动射击中!`)
                } else {
                    user.msg(`只能锁定大型飞行目标`)
                }
            }
            return
        }
        Role[user.role].action(e)

    } else if (button == GameButtonType.CROUCH) {
        if (user.data.wear.盾 == 'shH1') {
            weaponEffect(user, undefined, undefined, 'shH1')
        }
    }

})

function str(text, user, npc) {
    return text.constructor === Function ? text(user, npc) : text
}

function discounted(price, discount) {
    return (price * discount) | 0
}

async function npcDlg(user, npc, data) {
    if (data.job) {
        return data.job(user, npc)
    }
    if (data.text.constructor === Array) {// multi-text
        data = data.text.find(e => e.cond(user, npc)) || {}
        if (data.seq) {
            for (const e of data.text) {
                await user.dlgMsg(e, npc.interactHint, !data.text.isLastElem(e))
            }
            data.seq.constructor === Function && data.seq(user, npc)
            return
        }
    }

    let batch = 1
    let mode = '零售模式'
    if (user._altMode) {
        if (user.role === 'T') {
            batch = 5
        } else {
            batch = 3
        }
        mode = batch + '件批发模式'
    }

    let isOnce = false
    while (user.ready) {
        if (data.shop) {
            if (data.qualify) {
                const canNotBuy = data.qualify(user)
                if (canNotBuy) {
                    await user.dlgMsg(canNotBuy)
                    return
                }
            }
            const list = data.shop()
            const count = user.data.item.count()
            let discount = user.role === 'T' ? Role.T.atk(user) : 1
            const options = list.map(id => {
                const item = ItemDict[id]
                if (canBuy(user, id)) {
                    const quant = count[id]
                    return `${item.name} (${batch * discounted(item.price, discount)}${CURRENCY})${quant ? ` 已有${quant}件` : ''}`
                } else {
                    return `[${useBy(id)} 专用] ${item.name}`
                }
            })
            list.push('离开')
            let selection = await user.dlgSel(str(data.text, user, npc), `${npc.interactHint} (${mode})`, options, '购货单2')
            if (isNullSelect(selection) || selection.value.startsWith('离开')) return;
            const item = ItemDict[list[selection.index]]
            if (await buyItem(user, item.id, discounted(item.price, discount), false, batch)) {
                data.deal && data.deal(user, item)
            }
        }
        else if (data.options) {
            const key = data.once + user.player.userKey
            if (data.once) {
                if (npc._mem[key]) {
                    data = data.options[data.once]
                    if (data.run) {
                        return await data.run(user, npc, data)
                    }
                }
                if (isOnce) {
                    return
                }
            }
            const list = Object.entries(data.options).filter(([k, e]) => !e.cond || e.cond(user, npc))
            let selection = await user.dlgSel(str(data.text, user, npc), npc.interactHint, list.map(([k, v]) => {
                let price = v.price
                if (price) {
                    if (price.constructor === Function) {
                        price = price(user, npc)
                    }
                    k += ` (${price}${CURRENCY})`
                } else if (v.label) {
                    k = v.label(user, npc, k)
                }
                return k
            }))
            if (isNullSelect(selection)) return;

            if (selection.index !== list.length - 1 && data.once) {
                if (!npc._mem[key]) {
                    npc._mem[key] = true
                    isOnce = true
                }
            }

            let pick = list[selection.index][1]

            if (pick.options || pick.text) {
                const runState = await npcDlg(user, npc, pick)
                if (runState === Choice.CLOSE || pick.close) return;
                continue
            } else {
                if (pick.run) {
                    const runState = await pick.run(user, npc, data)
                    if (runState === Choice.CLOSE) {
                        return Choice.CLOSE
                    }
                }
                return
            }
        } else {
            await user.dlgMsg(data.text.constructor === Function ? data.text(user) : data.text, npc.interactHint)
            return
        }
    }
}
function sphereExplode(vox, cx, cy, cz, radius = 5) {
    cx = Math.round(cx)
    cy = Math.round(cy)
    cz = Math.round(cz)
    var xend = cx + radius
    var yend = cy + radius
    var zend = cz + radius
    for (var x = cx - radius; x <= xend; x++) {
        for (var y = cy - radius; y <= yend; y++) {
            for (var z = cz - radius; z <= zend; z++) {
                var dx = x - cx;
                var dy = y - cy;
                var dz = z - cz;
                const r = Math.round(Math.sqrt(dx * dx + dy * dy + dz * dz))

                if (r < radius) {
                    glitch(x, y, z) || voxels.setVoxelId(x, y, z, vox)
                } else if (r === radius) {
                    glitch(x, y, z) || voxels.getVoxel(x, y, z) && voxels.setVoxelId(x, y, z, lava())
                }
            }
        }
    }
}

function glitch(x, y, z) {
    if (y === 0 || (y <= 27 && (x === 0 || z === 0 || x === 254 || z === 254))) {
        voxels.setVoxelId(x, y, z, GLITCH.pickOne())
        return true
    }
    return false
}

function lava() {
    return Math.random() > 0.5 ? LAVA02 : LAVA01
}

function isLava(id) {
    return id === LAVA02 || id === LAVA01
}

function enemyDamage(x, y, z, vox) {
    if (y === 0) {
        voxels.setVoxelId(x, y, z, GLITCH.pickOne())
    } else if (vox === LAVA01 || vox === LAVA02) {
        voxels.setVoxelId(x, y, z, 0)
    } else if (vox) {
        voxels.setVoxelId(x, y, z, lava())
    }
}

async function saveMap() {
    const xmin = 0
    const ymin = 0
    const zmin = 0
    const xmax = 320
    const ymax = 63
    const zmax = 320
    const getvox = voxels.getVoxelId
    for (var y = ymin; y <= ymax; y++) {
        for (var x = xmin; x <= xmax; x++) {
            for (var z = zmin; z <= zmax; z++) {
                const vox = getvox(x, y, z)
                if (vox) {
                    MAP[`${x},${y},${z}`] = vox
                }
            }
        }
        await sleep(1);
    }
}

async function repairCity() {
    const setvox = voxels.setVoxelId
    let n = 0;
    for (const pos in MAP) {
        const vox = MAP[pos]
        setvox(...pos.split(','), vox)
        n++;
        if (n % 400 == 0) {
            await sleep(1);
        }
    }
}
global.repairCity = repairCity

function newWave(size = 1, xx = 127, yy = 78, zz = 127) {
    size = Math.pow(1, 0.25) | 0
    const margin = 35

    let makeEnemy

    if (bigBossBattle) {
        // log('ws', waveShips)
        if (waveShips <= 0) { return }
        // makeEnemy = (pos) => spawn('1luxifa', pos)
        if (!world.querySelector('#boss')) {
            bigBoss = spawn('1luxifa', [127, 220, 127])
            bigBoss.id = 'boss'
            bigBoss.addTag('ship')
            bigBoss.y = yy + 30
            bossShip(bigBoss, size, 1)

        }

        makeEnemy = (pos) => spawn('路西法舰队', pos)

        const r = 30
        const PI2 = Math.PI * 2
        for (var i = 0; i < PI2; i += PI2 / 12) {
            const x = 127 + Math.cos(i) * r
            const z = 127 + Math.sin(i) * r
            const e = makeEnemy([x, 260, z])

            e.addTag('ship')
            e.y = yy + 20
            alienShip(e, size, i, true)
            if (Math.random() < 0.5) {
                enemyDropDrone(e.position, 2)
            }
            // log('ship')
        }
        waveShips -= 12
    } else {
        const rot = Math.floor(Math.random() * 4) * PI2 + PI4
        makeEnemy = (pos) => spawn('ufo' + Math.floor(Math.random() * 5), pos)
        // makeEnemy = (pos) => spawn('路西法舰队', pos)
        for (var x = -size; x <= size; x++) {
            for (var z = -size; z <= size; z++) {
                for (var y = 0; y <= 1; y++) {
                    if (waveShips-- <= 0) { return }
                    const odd = (y % 2) * 0.5
                    const pos = [xx + (x + odd) * margin, yy + y * 10 + 200, zz + (z + odd) * margin]
                    const e = makeEnemy(pos)
                    // const e = spawn('ufo0', pos)
                    e.meshScale = e.meshScale.scale(3)
                    e.addTag('ship')
                    e.y = yy + y * 10
                    // e.meshEmissive = 2
                    // e.meshColor.set(0,1,1,1)
                    alienShip(e, size, rot)
                }
            }
        }
    }

}
global.newWave = newWave

function cubeRepair(xx, yy, zz, size) {
    const xmin = xx - size
    const ymin = yy - size
    const zmin = zz - size
    const xmax = xx + size
    const ymax = yy + size
    const zmax = zz + size
    const setvox = voxels.setVoxelId
    const getvox = voxels.getVoxelId
    let score = 0
    for (var x = xmin; x <= xmax; x++) {
        for (var z = zmin; z <= zmax; z++) {
            for (var y = ymin; y <= ymax; y++) {
                const vox = MAP[`${x},${y},${z}`]
                if (vox && vox !== getvox(x, y, z)) {
                    setvox(x, y, z, vox)
                    score++
                }
            }
        }
    }
    return score
}

function crossRepair(x, y, z) {
    const setvox = voxels.setVoxelId
    const getvox = voxels.getVoxelId
    let score = 0
    for (var pos of [
        [x, y, z],
        [x + 1, y, z],
        [x - 1, y, z],
        [x, y + 1, z],
        [x, y - 1, z],
        [x, y, z + 1],
        [x, y, z - 1],
    ]) {
        const vox = MAP[pos]
        if (vox && vox !== getvox(...pos)) {
            setvox(...pos, vox)
            score++
        }
    }
    return score
}


async function miniFireworks(targetPosition) {

    const portalTargetEntity = world.createEntity({
        position: targetPosition,
        fixed: true,
        collides: false,
        gravity: false,

        particleRate: 300,
        particleVelocitySpread: [20, 20, 20],
        particleLifetime: 2,
        particleSize: [50, 35, 25, 15, 5],
        particleSizeSpread: 2,
        particleColor: p_red_color,
        particleAcceleration: [0, -20, 0],
        bounds: [0, 0, 0],
        particleDamping: -0.5,
    });

    let i = 0;
    setInterval(() => {
        portalTargetEntity.particleColor = clr[(i++) % clr.length]
    }, 100)

}
// miniFireworks([64, 64, 64])


function cubeKill(xx, yy, zz, size) {
    const xmin = xx - size
    const ymin = yy - size
    const zmin = zz - size
    const xmax = xx + size
    const ymax = yy + size
    const zmax = zz + size
    const setvox = voxels.setVoxelId
    for (var x = xmin; x <= xmax; x++) {
        for (var z = zmin; z <= zmax; z++) {
            for (var y = ymin; y <= ymax; y++) {
                setvox(x, y, z, 0)
            }
        }
    }
}

var e_cnt = 0
for (const e of world.querySelectorAll('*')) {
    // if (e.id.endsWith('npc-1')) console.log(e.id)
    if (NPC[e.id]) {
        e.jobNPC(NPC[e.id])
    } else {
        // if (e_cnt >= 74) {
        //     e.destroy()
        // }
        log(e.id, e_cnt)
    }
    e_cnt++
    // e.enableDamage = true
}

function killEnemy() {
    world.querySelectorAll('.ship').map(e => e.hp = 0)
}
global.killEnemy = killEnemy
// world.sunPhase = 0.25

async function enemyShoot(shooter, target, speed = 3, mesh = 'beam1', atk = 4, yOffset = 0) {
    if (waveBullets-- < 0) return
    const src = shooter.position.clone()
    src.y += yOffset
    let tg = target.position
    let dx = tg.x - src.x
    let dy = (tg.y + 1) - src.y
    let dz = tg.z - src.z
    let len

    if (Math.random() < 0.8) {
        len = Math.sqrt(dx * dx + dy * dy + dz * dz)
        const tv = target.velocity.scale(len / speed)
        tg = target.position.add(tv)

        dx = tg.x - src.x
        dy = tg.y - src.y
        dz = tg.z - src.z

    }

    len = Math.sqrt(dx * dx + dy * dy + dz * dz) / speed
    dx /= len
    dy /= len
    dz /= len

    const e = spawn(mesh, src)
    e.velocity.set(dx, dy, dz)
    e.addTag('eBullet')
    e.meshEmissive = 0.5

    const rotx = Math.atan2(dy, speed)

    e.meshOrientation = Quat.rotateZ(-rotx).rotateY(Math.atan2(dz, dx) + PI2)
    e.collides = true
    e.onVoxelContact(({ x, y, z, voxel }) => {
        if (e.done) return
        enemyDamage(x, y, z, voxel)
        e.done = true
        e.destroy()
    })
    e.onEntityContact(({ other }) => {
        if (e.done) return
        const dmg = enemyToPlayerDamage(other, atk)
        hurtPlayer(other, dmg)
        e.done = true
        e.destroy()
    })

    await sleep(8000)
    e.done = true
    e.destroy()
}

world.onEntityCreate(({ entity }) => {
    if (entity.hasTag('eBullet')) {
        entity.meshScale = entity.meshScale.scale(0.2);
    }
})
var isBattling = false
var nextWaveMS = Date.now() + NextWaveMS

var waveBullets = 0
var waveBombs = 0
var waveDrones = 0
var waveShips = 0
var waveLv = 0
var waveAtk = 30
var waveCnt = 0

function canAlienRetreat() {
    return (waveBullets <= 0 || waveBombs <= 0)
}

function peace(now) {
    isBattling = false
    nextWaveMS = now + NextWaveMS
    worldEasing('maxFog', 0.8)
    for (const e of world.querySelectorAll('.eBullet,.pBullet')) {
        e.destroy()
    }
    for (const e of world.querySelectorAll('.eDrone')) {
        e.selfDestruct()
    }
    // world.ambientSound.sample = SE.peace.sample
    for (const p of players()) { changeMusic(p) }
}

function war() {
    waveCnt++
    const active_players = activePlayers()
    waveLv = Math.min(100, active_players.length)

    isBattling = true;
    Difficulty = Math.max(1.1 - waveLv * 0.011, 0.12)

    // const playerList = active_players.sort((a, b) => b.def - a.def)
    const playerList = active_players.filter(player => !isAdmin(player)).sort((a, b) => b.def - a.def)
    const hero = playerList[1] || playerList[0]
    const heroDef = (hero && hero.def) || 10
    waveAtk = (heroDef / 3 + 4 + Math.random() * 2) | 0
    waveAtk = Math.round(waveAtk);
    if (waveAtk < 10) {
        waveAtk = 10
    }
    if (waveAtk > 30) {
        waveAtk = 30
    }
    waveAtk += Math.ceil(active_players.length / 2)
    if (bigBossBattle) {
        waveShips = 100 + 6 * waveLv
        waveBullets = 100000000
        waveBombs = 50000
        waveDrones = 3000
        waveAtk += 5;
    } else {
        waveShips = Math.min(Math.ceil(50 + 3 * waveLv), 300)
        waveBullets = Math.min(60 * waveShips / Difficulty, 40000)
        waveBombs = Math.min((waveBullets / 5) | 0, 4000)
        waveDrones = Math.min(waveBombs, 300) + active_players.filter(e => e.data.role === 'W').length * 10
    }
    if (waveAtk > 60) {
        waveAtk = 60
    }

    worldEasing('maxFog', 0)
    // world.ambientSound.sample = SE.mask.sample
    for (const p of players()) { changeMusic(p) }

    logger.log(`[Info] Wave<${waveCnt}> started with Difficulty<${Difficulty}>, waveLv<${waveLv}>, waveAtk<${waveAtk}>, bigBoss<${bigBossBattle}>`);
}

async function worldEasing(name, n) {
    while (true) {
        const diff = Math.sign(n - world[name])
        world[name] += diff * 0.01
        if (Math.abs(diff) < 0.1) {
            world[name] = n
            break
        }
        await sleep(60)
    }
}
global.worldEasing = worldEasing

function hideNonActive() {
    for (const p of nonActivePlayers()) {
        p.ghost(true)
        quitGame(p)
        p.dlgMsg('挂机玩家进入观众模式, 请重新进入游戏')
    }
}
function rewardAfterBattle(contrib, boss = 0) {
    for (const p of activeContribPlayers()) {
        if (p.data.gqReward >= 0) {
            p.data.gqReward += 1;


            if (p.data.gqReward == 3) {
                p.gainExp(19491);
                dialog(p, '国庆礼包1！', `打完3次波数，获得国庆礼物19491经验！`, ['开心收下'])
            }

        }
        p.contrib += contrib
        p.roundContrib = 0
        p.bossReward = (p.bossReward || 0) + boss
        saveTempStat(p)
    }
}
async function masterMind() {
    while (true) {
        const now = Date.now()
        try {
            if (isBattling) {
                Prey = world.querySelectorAll('player,.turret').filter(e => (e.isPrey && e.hp > 0) && (!e.isPlayer || e.ready))
                await sleep(2000)
                const ships = world.querySelectorAll('.ship')

                if (bigBossBattle) {
                    if (bigBoss.destroyed) {
                        world.say(`${BossName}被${bigBoss._attacker ? bigBoss._attacker.name() : ''}击败, 战斗中的活跃成员奖励${BossContrib}贡献值并获得1次流浪商人购物机会`)
                        rewardAfterBattle(BossContrib, 1)
                        hideNonActive()
                        peace(Date.now() + 1000 * 120)
                        for (const e of world.querySelectorAll('#路西法叛徒-1')) {
                            e.meshInvisible = true
                            e.enableInteract = false
                        }
                        bigBossBattle = false
                        logger.log(`[Info] Wave<${waveCnt}> finished (Lucifa all clear)`);
                    } else {
                        // boss retreat?
                        if (ships.length < Math.max(15, activePlayers().length / 2)) {
                            newWave(waveLv)
                            if (Math.random() < 0.5) {
                                newWave(waveLv);
                                if (Math.random() < 0.5) {
                                    newWave(waveLv);
                                }
                            }
                        }
                    }
                }
                else if (canAlienRetreat()) {
                    world.say(`${AlienName}撤退, 请抓紧时间修整准备下一轮防卫战`)
                    hideNonActive()
                    peace(now)
                    waveShips = 0
                    logger.log(`[Info] Wave<${waveCnt}> finished (Return)`);
                }
                else if (ships.length <= 0) {
                    world.say(`${AlienName}被全歼, 战斗中的活跃成员奖励${WinContrib}贡献值, 请抓紧时间修整准备下一轮防卫战`)
                    rewardAfterBattle(WinContrib)
                    hideNonActive()
                    peace(now)
                    logger.log(`[Info] Wave<${waveCnt}> finished (All clear)`);
                }
                else if (ships.length < Math.max(15, activePlayers().length / 1.5)) {
                    try {
                        newWave(waveLv)
                        if (Math.random() < 0.3) {
                            newWave(waveLv);
                            if (Math.random() < 0.3) {
                                newWave(waveLv);
                            }
                        }
                    } catch (e) {
                        world.say(e.stack)
                        logger.log(`[Error] masterMind => newWave<${waveLv}>: ${e}`);
                    }
                }
            } else {
                const secLeft = ((nextWaveMS - now) / 1000) | 0
                dxsj = `⏱️${secLeft}秒后`
                if (secLeft <= 0) {
                    war()
                    try {
                        newWave(waveLv)
                    } catch (e) {
                        world.say(e.stack)
                        logger.log(`[Error] masterMind => newWave<${waveLv}>: ${e}`);
                    }
                    if (bigBossBattle) {
                        wxrname = '����路西法精锐舰队��';
                        world.say(`"${BossName}精锐舰队"到达本市上空, 请做好迎战准备`)
                        dxsj = `交战中`
                    } else {
                        wxrname = '��异星舰队';
                        world.say(`${AlienName}到达本市上空, 请做好迎战准备`)
                        dxsj = `交战中`
                    }
                } else {
                    if (secLeft <= 10 || secLeft % 10 === 0) {
                        if (bigBossBattle) {
                            world.say(`一级危险警报! "${BossName}精锐舰队"接近中, 还有${secLeft}秒到达本市上空!`)
                            wxrname = `����${BossName}精锐舰队��`
                        } else {
                            world.say(`下一波${AlienName}还有${secLeft}秒来袭`)
                            wxrname = `��异星舰队`
                        }
                    }
                }


            }
        } catch (e) {
            world.say(e.stack);
            logger.log(`[Error] masterMind: ${e}`);
        }
        await sleep(1000)
        checkData();
    }
}
function checkData() {
    if (bigBossBattle && isBattling) {
        // Check Lucifa
        const lucifa = world.querySelector('#boss');
        if (!lucifa || !bigBoss || !("destroyed" in lucifa) || !("destroyed" in bigBoss)) {
            world.say("路西法数据异常，强制重启容器");
            checkData = () => { };
            restart("路西法数据异常");
            logger.log(`[Warning] Data exception: Cannot find Entity<"#boss">, force destroy the container`);
        }
        // Check Lucifa Crackers
        const lucifaCrackers = world.querySelectorAll('#路西法叛徒-1');
        if (lucifaCrackers.length <= 0) {
            world.say("路西法叛徒数据异常，装甲强制破解");
            boss.guard = false;
            logger.log(`[Warning] Data exception: Cannot find Entity<"#路西法叛徒-1">, boss.guard<true> => false`);
        }
        for (const e of lucifaCrackers) {
            if (e.meshInvisible) {
                world.say("路西法叛徒数据异常");
                e.meshInvisible = false;
                logger.log(`[Warning] Data exception: Entity<"#路西法叛徒-1">.meshInvisible<true> => false`);
            }
            if (!e.enableInteract) {
                world.say("路西法叛徒数据异常");
                e.enableInteract = true;
                logger.log(`[Warning] Data exception: Entity<"#路西法叛徒-1">.enableInteract<false> => true`);
            }
        };
    }
}

const Quat90 = Quat.rotateY(-Math.PI / 2)
async function enemyDropDrone(sp, isElite = 0) {
    if (waveDrones-- < 0 || world.querySelectorAll('.eDrone').length > 20) return
    let mesh = ''
    let quat
    let scale = 2
    let jump
    let speed
    let hp = 150;
    let fly = false
    if (isElite == 4) {
        mesh = '深空异骸';
        quat = Quat90
        hp = 600;
        scale = 0.6;
        jump = 1;
        speed = 1.5;
        fly = true;
    } else if (isElite == 3) {
        mesh = '毁灭者装甲';
        quat = Quat.rotateY(Math.PI)
        hp = 900;
        scale = 1.2;
        jump = 0.3;
        speed = 0.2;
    } else if (isElite == 2) {
        mesh = '腐蚀者';
        quat = Quat90
        hp = 150;
        scale = 0.8;
        jump = 1;
        speed = 0.8;
        fly = true;
    } else if (isElite == 1) {
        mesh = ['路西法钳子虫怪', '路西法蝎子虫怪', '路西法甲虫怪'].pickOne()
        hp = 200;
        quat = Quat90
        scale = 0.75
        jump = 0.3 + Math.random() * 0.1
        speed = 0.35
        if (mesh == '路西法甲虫怪') {
            speed *= 2;
            jump *= 2;
        } else {
            hp *= 1.5;
        }
    } else {
        mesh = 'bot' + Math.floor(Math.random() * 4)
        quat = Quat
        jump = 0.5 + Math.random() * 0.2
        speed = 0.5 + Math.random() * 0.15
        if (Math.random() < 0.2) {
            hp *= 4;
            scale *= 2.5
            speed /= 1.3;
        } else if (Math.random() < 0.2) {
            speed *= 1.4;
            jump *= 1.2;
        }
    }
    const e = spawn(mesh, sp)
    e.hp = e.maxHp = hp;
    e.gravity = true
    if (isElite == 2) {
        e.gravity = false;
        e.particleRate = 20;
        e.motion.setDefaultMotionByName('gao')
    }
    if (isElite == 4) {
        e.gravity = false;
        e.collides = false;
        e.particleRate = 20;
        e.particleSize *= 3;
        e.particleColor = new GameRGBColor(0, 0, 1);
        e.state = 'fly';
        e.addTag('子弹抗性');
    }
    if (isElite == 3) {
        e.addTag('子弹抗性');
        setTimeout(() => {
            e.motion.setDefaultMotionByName('行走')
        }, 1000)
    }
    e.addTag('eDrone')
    e.collides = true
    e.meshScale.x *= scale
    e.meshScale.y *= scale
    e.meshScale.z *= scale
    e.friction = 0.2
    e.isEnemy = true
    e.enableDamage = true
    e.selfDestruct = async () => {
        e.glowRed()
        e.particle()
        e.done = true
        await sleep(1000)
        e.destroy()
    }
    e.onEntityContact(async ({ other }) => {
        if (e.done || !other.isPrey || other.hp <= 0) return
        if (speed <= 0) return;
        if (e.hp < e.maxHp / 2) {
            let dmg = enemyToPlayerDamage(other, 20 * e.hp / e.maxHp)
            if (other.isPlayer && other.data.wear.衣服 == 'suitD1') { dmg *= 0.25; }
            if (hurtPlayer(other, dmg)) {
                if (other.isPlayer) {
                    ripEquip(other)
                }
            }
            e.velocity.set(0, 0, 0)
            e.done = true;
            e.selfDestruct();
        } else {
            let dmg = enemyToPlayerDamage(other, 5 * e.hp / e.maxHp)
            if (other.isPlayer && other.data.wear.衣服 == 'suitD1') { dmg *= 0.25; }
            if (hurtPlayer(other, dmg)) {
                if (other.isPlayer) {
                    if (Math.random() < 0.15) {
                        ripEquip(other)
                    }
                }
            }
            e.velocity.y += 0.2;
            e.hurt(e.maxHp / 20);
            let sp = speed + 1 - 1;
            speed = 0;
            setTimeout(() => { speed = sp }, 500)
        }
    })

    e.onVoxelContact(async () => {
        if (e.done || (isElite == 2)) return
        if (isElite) {
            await sleep(1000 + 500 * Math.random())
        }
        e.velocity.y = jump
    })
    e.onDie(() => {
        e.selfDestruct()
    })

    let target
    let tp = e.position.clone();
    let i = 0
    let ld = [0, 0];

    while (!e.done) {
        if (!isBattling) {
            e.selfDestruct()
        }
        if (i % 30 === 0) {
            target = e.nearestAliveEntities(Prey)[0]
        }
        if (target) {
            if (!fly || (i % 20 == 0)) {
                tp = target.position.clone();
                if (fly) {
                    if (!e.state || e.state == 'fly') {
                        tp.addEq(new GameVector3(random(-20, 20), random(5, 20), random(-20, 20)))

                    }
                }
            }
            if (!fly) {
                let dd = e.position.distance(target.position);
                if (Math.abs(ld[0] - dd) < 0.2 || dd > 40) {//不在移动或者离目标非常远
                    ld[1] += 1;
                    ld[0] = dd;
                } else {
                    ld = [0, 0];
                }
                if (ld[1] > 100) {
                    e.selfDestruct();
                }
            }

            if ((isElite == 1 && (i % 8 === 0) && Math.random() < 0.5) || (isElite == 2 && (i % 16 === 0))) {
                target && target.hp > 0 && enemyShoot(e, target, 6)
                if (isElite == 2 && i % 64 == 0 && Math.random() < 0.5) {
                    target && target.hp > 0 && enemyMissile(e, target, '大号的导弹', 1, 'eBullet', 10)

                }
            }
            if (isElite == 3) {
                if (e.hp < e.maxHp) {
                    e.hp += 5;
                    if (e.hp > e.maxHp) {
                        e.hp = e.maxHp;
                    }
                }
                if (i % 120 == 0) {
                    speed += 2;
                    e.velocity.y = 1;
                    e.particleRate = 100;
                    setTimeout(() => { speed -= 2; e.particleRate = 0 }, 2000)
                }
                if (i % 80 == 0) {
                    e.motion.setDefaultMotionByName('射击')
                    e.removeTag('子弹抗性');
                    await sleep(700);
                    for (let jj = 0; jj < 4; jj++) {
                        enemyMissile(e, target, '大号的导弹', 1, 'eBullet', 10)
                        e.velocity.set(0, 0, 0);
                        await sleep(200);
                    }
                    e.addTag('子弹抗性');
                    e.motion.setDefaultMotionByName('行走')
                }
            }
            if (isElite == 4) {
                if (i > 14 * 60) {//存活2分钟

                    if (e.state == 'fly' && target) {
                        (async function () {
                            speed = 0;
                            e.removeTag('子弹抗性');
                            for (let jj = 0; jj < 10; jj++) {
                                e.meshScale = e.meshScale.scale(0.95);
                                await sleep(1000);
                            }
                            e.hp = e.maxHp = 1000;
                            speed = 1;
                            e.mesh = 'mesh/异骸武士.vb';
                            e.addTag('子弹抗性')
                            e.meshScale = new GameVector3(0.06, 0.06, 0.06);
                            e.collides = true;
                            e.gravity = true;
                            e.state = 'land';
                            e.position.copy(target.position.add(new GameVector3(0, 1, 0)));
                        })()
                    }
                    if (e.state == 'land') {
                        if (e.position.distance(target.position) > 20) {
                            //                    e.meshInvisible = true;
                            e.meshScale = new GameVector3(0.03, 0.03, 0.03);
                        } else {
                            e.meshInvisible = false;
                            e.meshScale = new GameVector3(0.08, 0.08, 0.08);

                        }
                        if (i % 60 == 0 && e.meshInvisible == false) {
                            (async function () {
                                for (let jj = 0; jj < 15; jj++) {
                                    e.nearestAliveEntities(Prey).forEach((p) => {
                                        if (p.position.distance(e.position) < 8 && p.hp > 0) {
                                            hurtPlayer(p, 6);
                                            e.hp += 5;
                                            if (e.hp > e.maxHp) {
                                                e.hp = e.maxHp;
                                            }
                                        }
                                    })
                                    e.meshOrientation = e.meshOrientation.rotateY(Math.PI / 10)
                                    await sleep(200);
                                }
                            })();
                        }
                        if ((i - 2) % 60 == 0) {
                            e.position.copy(target.position);
                            e.velocity.y += 0.5;
                            e.hp += e.maxHp / 5;
                            if (e.hp > e.maxHp) {
                                e.hp = e.maxHp;
                            }
                        }
                        if (i % 30 == 0) {
                            if (target && target.hp > 0) {
                                enemyMissile(e, target, '深空异骸飞刃', 4, 'eBullet', 12);
                            }
                        }
                    }
                } else {
                    if (i % 50 == 0) {
                        if (target && target.hp > 0) {
                            (async function () {
                                for (let jj = 0; jj < 5; jj++) {
                                    enemyShoot(e, target, 6, '深空异骸射流', 5)
                                    await sleep(300);
                                };
                            })();
                        }

                    }
                    if (i % 80 == 0) {
                        if (target && target.hp > 0) {
                            enemyMissile(e, target, '深空异骸飞弹', 2, 'eBullet', 20);
                        }

                    }

                }
            }

            let dx = tp.x - e.position.x
            let dz = tp.z - e.position.z
            // let dy = target.position.y - e.position.y
            let dist = Math.sqrt(dx * dx + dz * dz) / speed
            dx /= dist
            dz /= dist
            e.meshOrientation = quat.rotateY(Math.atan2(dz, dx))
            if (dist > 0.5 || isElite == 2) {
                e.velocity.set(dx, e.velocity.y, dz)
                if (isElite == 2 && Math.abs(tp.y, e.position.y) > 3) {
                    e.velocity.y = (e.velocity.y * 9 + Math.abs(tp.y - e.position.y) / (tp.y - e.position.y) * 2) / 10
                }
            } else {
                e.velocity.set(0, 0, 0)
            }
        } else {
            e.velocity.set(0, 0, 0)
        }
        await sleep(70)
        i++
    }
}

function enemyDropBomb(shooter) {
    if (waveBombs-- < 0) return
    const e = spawn('小导弹', shooter.position)
    e.gravity = true
    e.addTag('eBullet')
    e.collides = true
    e.meshScale.x *= 15
    e.meshScale.y *= 8
    e.meshScale.z *= 15
    e.glowRed()
    e.onVoxelContact(async ({ x, y, z, voxel }) => {
        if (e.done) return
        // enemyDamage(x, y, z, voxel)
        e.done = true
        e.particle()
        await sleep(500)
        e.destroy()
    })

    e.onEntityContact(async ({ other }) => {
        if (e.done) return;
        let dmg = enemyToPlayerDamage(other, 35)
        if (other.isPlayer && other.data.wear.帽子 == 'hatD1') { dmg *= 0.25; }

        if (other.hasTag('defense')) {
            dmg *= 6;
        }
        if (hurtPlayer(other, dmg)) {
            if (other.isPlayer) {
                ripEquip(other)
            }
        }
        e.done = true
        e.particle()
        await sleep(500)
        e.destroy()
    })
    e.onDestroy(() => {
        sphereExplode('', e.position.x, e.position.y, e.position.z, 3)

        world.querySelectorAll('player,.defense,.turret').forEach((p) => {
            if (p.position.distance(e.position) < 10) {
                let num = (10 - p.position.distance(e.position)) / 10;
                let dir = p.position.sub(e.position);
                dir = dir.scale(1 / dir.mag());
                if (p.isPlayer) {
                    p.velocity.addEq(dir.scale(num * 1.5));
                } else {
                }
                let dmg = enemyToPlayerDamage(p, 30 * num);
                if (p.isPlayer && p.data.wear.帽子 == 'hatD1') { dmg *= 0.25; }

                if (hurtPlayer(p, dmg)) {
                    if (p.isPlayer && Math.random() < 0.2 * num) {
                        ripEquip(p)
                    }
                }
            }
        })
    })
    return e
}

function gunShoot(shooter, target, mesh = 'beam0', speed = 3, tag = 'eBullet', data) {
    const src = shooter.position
    let tg = target.position

    let dx = tg.x - src.x
    let dy = tg.y - src.y
    let dz = tg.z - src.z

    let len = Math.sqrt(dx * dx + dy * dy + dz * dz)

    const tv = target.velocity.scale(len / speed)
    tg = target.position.add(tv)

    dx = tg.x - src.x
    dy = tg.y - src.y
    dz = tg.z - src.z

    len = Math.sqrt(dx * dx + dy * dy + dz * dz) / speed
    dx /= len
    dy /= len
    dz /= len

    const e = spawn(mesh, shooter.position)
    e.velocity.set(dx, dy, dz)

    const rotx = Math.atan2(dy, speed)

    e.meshOrientation = Quat.rotateZ(-rotx).rotateY(Math.atan2(dz, dx) + PI2)
    e.collides = true

    e.addTag(tag)
    const pj = Projectile[mesh] || {}
    e.meshEmissive = pj.emissive || 0.5
    e.onVoxelContact(() => {
        if (e.hit) return
        e.hit = true
        e.destroy()
    })

    const pilot = shooter.pilot
    const deployer = shooter.deployer
    e.onEntityContact(({ other }) => {
        if (e.hit) return
        if (other.isUfo) {
            turretDamageToEnemy(pilot, deployer, other, data.atk)
            e.hit = true
            e.destroy()
        }
    })
}

async function enemyMissile(shooter, target, mesh = 'beam0', speed = 1, tag = 'eBullet', atk = 15) {
    const e = spawn(mesh, shooter.position)

    e.collides = true
    e.gravity = false
    e.position.y += 1
    e.addTag('eMissile')
    e.addTag(tag)
    const pj = Projectile[mesh] || {}
    e.meshEmissive = pj.emissive || 0.5

    const pilot = shooter.pilot
    const deployer = shooter.deployer
    e.onVoxelContact(({ x, y, z, voxel }) => {
        if (e.hit) return
        enemyDamage(x, y, z, voxel)
        e.hit = true
        e.destroy()
    })
    e.onEntityContact(({ other }) => {
        if (e.hit) return
        const dmg = enemyToPlayerDamage(other, atk)
        hurtPlayer(other, dmg)
        e.hit = true
        e.destroy()
    })
    e.onDestroy(() => {
        sphereExplode('', e.position.x, e.position.y, e.position.z, 2)

        world.querySelectorAll('player,.defense,.turret').forEach((p) => {
            if (p.position.distance(e.position) < 6) {
                let num = (6 - p.position.distance(e.position)) / 6;
                let dir = p.position.sub(e.position);
                dir = dir.scale(1 / dir.mag());
                if (p.isPlayer) {
                    p.velocity.addEq(dir.scale(num * 1.5));
                } else {
                }
                let dmg = enemyToPlayerDamage(p, 20 * num);
                if (p.isPlayer && p.data.wear.帽子 == 'hatD1') { dmg *= 0.5; }

                if (hurtPlayer(p, dmg)) {
                    if (p.isPlayer && Math.random() < 0.2 * num) {
                        ripEquip(p)
                    }
                }
            }
        })
    })

    while (!e.hit) {
        // world.onTick(() => {
        let tg = target.position.add(target.velocity.scale(10))
        let dx = tg.x - e.position.x
        let dy = tg.y - e.position.y
        let dz = tg.z - e.position.z

        const len = Math.sqrt(dx * dx + dy * dy + dz * dz) / speed
        dx /= len
        dy /= len
        dz /= len
        e.velocity.copy(new GameVector3(dx, dy, dz).add(e.velocity.scale(3)).scale(1 / 4))
        speed += 0.2;
        const rotv = Math.atan2(dy, speed)
        const rot = Math.atan2(dz, dx)

        e.meshOrientation = Quat.rotateZ(-rotv).rotateY(rot + PI2)
        await sleep(200)
    }
    // })
    // return
    e.gravity = true
    const v = e.velocity
    e.velocity.set(v.x * 0.3, v.y * 0.3, v.z * 0.3)
    await sleep(1000)

    e.destroy()
}

async function missile(shooter, target, mesh = 'beam0', speed = 1, tag = 'eBullet', data) {
    const e = spawn(mesh, shooter.position)

    e.collides = true
    e.gravity = false
    e.position.y += 1

    e.addTag(tag)
    const pj = Projectile[mesh] || {}
    e.meshEmissive = pj.emissive || 0.5
    e.onVoxelContact(() => {
        e.hit = true;
    })

    const pilot = shooter.pilot
    const deployer = shooter.deployer
    e.onEntityContact(({ other }) => {
        if (e.hit) return
        if (other.isEnemy) {
            turretDamageToEnemy(pilot, deployer, other, data.atk)
            e.hit = true;
        }
    })

    const src = shooter.position
    const dst = target.position

    while (!e.hit) {
        // world.onTick(() => {
        let tg = dst.add(target.velocity.scale(10))
        let dx = tg.x - src.x
        let dy = tg.y - src.y
        let dz = tg.z - src.z

        speed += 0.5
        const len = Math.sqrt(dx * dx + dy * dy + dz * dz) / speed
        dx /= len
        dy /= len
        dz /= len
        e.velocity.copy(new GameVector3(dx, dy, dz).add(e.velocity.scale(3)).scale(1 / 4))

        const rotv = Math.atan2(dy, speed)
        const rot = Math.atan2(dz, dx)

        e.meshOrientation = Quat.rotateZ(-rotv).rotateY(rot + PI2)
        await sleep(100)
    }
    // })
    // return
    e.gravity = true
    const v = e.velocity
    e.velocity.set(v.x * 0.3, v.y * 0.3, v.z * 0.3)
    await sleep(1000)

    e.destroy()
}

async function droneFollow(user, drone) {
    const fd = user.player.facingDirection
    const pos = user.position
    const fly = 1.5
    let fdx = 0, fdz = 0
    // drone._ticker = world.onTick(() => {
    let i = 0
    let speed = 0.3
    const vel = drone.velocity
    while (drone.pilot) {
        const p = drone.position
        if (drone.target) {
            if (drone.target.destroyed || (Math.abs(vel.x + vel.y + vel.z) <= 0.001)) {
                drone.leave()
            }
            const tg = drone.target.position
            let dx = tg.x - p.x
            let dy = tg.y - p.y
            let dz = tg.z - p.z

            const dist = Math.sqrt(dx * dx + dy * dy + dz * dz)

            const sp = dist / speed
            dx /= sp
            dy /= sp
            dz /= sp
            speed += 0.1
            if (p.y > 64) {
                drone.collides = true
            }
            if (speed > 8) {
                speed = 8
            }

            vel.set(dx, dy, dz)
            drone.meshOrientation = Quat.rotateY(Math.atan2(dz, dx) - PI2)
        } else {
            if (user.player.walkState) {
                fdx = fd.x
                fdz = fd.z
            }
            const x = pos.x - fdx * 1.5
            const y = pos.y + fly + Math.sin(i * 0.3) * 0.05 + drone.bounds.y * 0.12
            const z = pos.z - fdz * 1.5

            const dx = x - p.x
            const dy = y - p.y
            const dz = z - p.z
            drone.meshOrientation = Quat.rotateY(Math.atan2(pos.z - p.z, pos.x - p.x) - PI2)
            let vx = dx * 0.3
            let vy = dy * 0.3
            let vz = dz * 0.3

            drone.velocity.set(vx, vy, vz)

            if (dx * dx + dy * dy + dz * dz > 25) {
                drone.collides = false
            } else {
                drone.collides = true
            }
            i++
        }
        await sleep(70)
    }
    vel.set(0, 0, 0)
    drone.gravity = true
    drone.collides = true
    delete drone.target
    drone.glowNone()
}

// async function poll(fn, text) {//轮询, 出错后等一段时间重试
//     while (true) {
//         try {
//             return await fn()
//         } catch (e) {
//             world.say(text || e.stack)
//             await sleep(300) //如果遇到query timeout, 等待300毫秒重试
//         }
//     }
// }

// async function mkTable(tbName, dict) {
//     const cmd = [`CREATE TABLE IF NOT EXISTS ${tbName} (${Object.entries(dict).map(([k, v]) => `${k} ${v}`).join(',')});`]
//     return await poll(() => db.sql(cmd))
// }

// async function sqlInsert(tbName, dict, key = 'userkey') {

//     const keyList = Object.keys(dict)
//     const valList = Object.values(dict).map((v, i) => {
//         if (v.constructor === Array || v.constructor === Object) {
//             return JSON.stringify(v)
//         }
//         return v
//     })
//     const cmd = [`INSERT INTO ${tbName} (${keyList})VALUES(`, ...(','.repeat(keyList.length - 1)), `) ON CONFLICT(${key}) DO NOTHING;`]
//     return await poll(() => db.sql(cmd, ...valList))
// }

// async function sqlUpdate(tbName, key, dict) {
//     let id = dict[key]
//     delete dict[key]
//     let keyList = Object.keys(dict)
//     const valList = Object.values(dict)
//     let head = keyList.shift()
//     let tail = keyList.map((e, i) => `,${e}=`)
//     const cmd = [`UPDATE ${tbName} SET ${head}=`, ...tail, ` WHERE ${key}=`, ';']
//     return await poll(() => db.sql(cmd, ...valList, id))
// }

// async function allPlayers() {
//     const list = await poll(() => db.sql`SELECT * FROM player;`)
//     for (const p of list) {
//         console.log(JSON.stringify(p))
//     }
// }
//global.allPlayers = allPlayers

async function timeLoop() {

    while (true) {
        const time = new Date(Date.now() + (8 * 60 * 60 * 1000))
        // const d = time.getDate()
        // const m = time.getMonth()
        const wday = time.getDay()
        const h = time.getHours()
        // if ((wday === 5 || wday === 6 || wday === 0) && (18 <= h && h <= 23) && activePlayers().length >= 0) {
        // log(wday, h, activePlayers().length)
        if ((wday === 5 || wday === 6 || wday === 0) && (10 <= h && h <= 21) && activePlayers().length >= 20) {
            logger.log(`[Info] Spawned Lucifa`);
            bigBossBattle = true
            boss_edu_score = 0
            for (const e of world.querySelectorAll('#路西法叛徒-1')) {
                e.meshInvisible = false
                e.enableInteract = true
            }
        } else {
            if (bigBossBattle == false) {
                for (const e of world.querySelectorAll('#路西法叛徒-1')) {
                    e.meshInvisible = true
                    e.enableInteract = false
                }
            }
        }

        if (h === 0) {
            Revive = {}
        }
        await sleep(3600000)
    }
}
initGame();
async function initGame() {
    // await poll(() => db.sql`select 1;`, 'PG启动中...')
    // await mkTable('player', {
    //     userkey: `VARCHAR(16) PRIMARY KEY DEFAULT ''`,
    //     username: `VARCHAR(50) DEFAULT ''`,
    //     role: `VARCHAR(2) DEFAULT ''`,
    //     coin: `INT DEFAULT 0`,
    //     exp: `INT DEFAULT 0`,
    //     edu_score: `INT DEFAULT 0`,
    //     item: `TEXT DEFAULT ''`,
    //     wear: `TEXT DEFAULT ''`,
    //     task: `TEXT DEFAULT ''`,
    //     lastactive: `BIGINT DEFAULT 0`,
    // })

    saveMap()//test
    masterMind()
    //碰撞过滤
    world.addCollisionFilter('.ship', '.ship')
    world.addCollisionFilter('.ship', '.eBullet')
    world.addCollisionFilter('.eDrone', '.eBullet')
    world.addCollisionFilter('.eBullet', '.eBullet')
    world.addCollisionFilter('.pBullet', '.pBullet')
    world.addCollisionFilter('.pBullet', '.eBullet')
    world.addCollisionFilter('.pBullet', '.turret')
    world.addCollisionFilter('.pBullet', '.defense')
    world.addCollisionFilter('.pBullet', '.dead')
    world.addCollisionFilter('.pBullet', 'player')
    world.addCollisionFilter('.pDrone', 'player')
    world.addCollisionFilter('.turret', 'player')
    world.addCollisionFilter('.turret', '.turret')
    world.addCollisionFilter('.defense', 'player')
    world.addCollisionFilter('.defense', '.defense')

    setTimeout(() => { world.removeCollisionFilter('player', 'player'); }, 5000)
    world.addZone({
        selector: '*',
        bounds: {
            lo: [-80, -5, -80],
            hi: [255 + 80, 200, 255 + 80],
        },
    }).onLeave(({ entity }) => {
        if (!entity.deployer) entity.destroy()
    })

    world.airFriction = 0
    world.breakVoxelSound.sample = world.placeVoxelSound.sample = ''
    // world.ambientSound.gain = 0.75
    // world.ambientSound.sample = SE.peace.sample

    timeLoop()

    // const boss = spawn('1luxifa', [127, 60, 127])
    // boss.addTag('ship')
    // boss.y = 230
    // bossShip(boss, 1)
    // boss.hp = 0
    // boss.collides = true
    // boss.deployer = 1
    // waveBullets = 2000
    // bigBoss = true
}

world.onPlayerJoin(async ({ entity }) => {
    while (!entity.destroyed) {
        remoteChannel.sendClientEvent(entity, {
            type: 'first',
            data: [
                `${wxrname}`,
                `${dxsj}`
            ]
        })

        await sleep(0.5 * 1000)

    }
})

/*const b1 = world.querySelector('#神装试穿');
b1.enableInteract = true; // 允许进行互动
b1.interactRadius = 3;   // 实体的互动范围
b1.interactHint = '一名士兵' // 互动提示框显示实体的名称

// 玩家与实体进行交互时触发
b1.onInteract(async ({ entity }) => {
    const result = await entity.player.dialog({
        type: GameDialogType.TEXT,   // 对话框的类型，TEXT是文本框。            // 相机镜头对准NPC
        title: '一名士兵',
        content: `这是我从抽奖机拿的新黄金“神装”。居然能提供5防御和20本能。手上的枪不需要子弹而且是连发的`,
    });
});


const s1 = world.querySelector('#海洋商人');
s1.enableInteract = true; // 允许进行互动
s1.interactRadius = 3;   // 实体的互动范围
s1.interactHint = '海界售货商' // 互动提示框显示实体的名称

// 玩家与实体进行交互时触发
s1.onInteract(async ({ entity }) => {
    const result = await entity.player.dialog({
        type: GameDialogType.TEXT,   // 对话框的类型，TEXT是文本框。            // 相机镜头对准NPC
        title: '海界售货商',
        content: `我特意远赴澜渦星，与当地居民完成交易，才换来这套特制的商人服饰和这根海界手杖。`,
    });
});

const y1 = world.querySelector('#皇家医生');
y1.enableInteract = true; // 允许进行互动
y1.interactRadius = 3;   // 实体的互动范围
y1.interactHint = '皇家医生' // 互动提示框显示实体的名称

// 玩家与实体进行交互时触发
y1.onInteract(async ({ entity }) => {
    const result = await entity.player.dialog({
        type: GameDialogType.TEXT,   // 对话框的类型，TEXT是文本框。            // 相机镜头对准NPC
        title: '皇家医生',
        content: `我可是超时空皇族的御用医师，掌握着逆转生死的超凡治愈之力。`,
    });
});

const w1 = world.querySelector('#忍者');
w1.enableInteract = true; // 允许进行互动
w1.interactRadius = 3;   // 实体的互动范围
w1.interactHint = '火焰武士' // 互动提示框显示实体的名称

// 玩家与实体进行交互时触发
w1.onInteract(async ({ entity }) => {
    const result = await entity.player.dialog({
        type: GameDialogType.TEXT,   // 对话框的类型，TEXT是文本框。            // 相机镜头对准NPC
        title: '火焰武士',
        content: `我自幼在熔岩之地淬炼筋骨，火焰武士族将这件传承熔岩圣力的圣装赐予于我。`,
    });
});

const g1 = world.querySelector('#雷电工人');
g1.enableInteract = true; // 允许进行互动
g1.interactRadius = 3;   // 实体的互动范围
g1.interactHint = '工程师' // 互动提示框显示实体的名称

// 玩家与实体进行交互时触发
g1.onInteract(async ({ entity }) => {
    const result = await entity.player.dialog({
        type: GameDialogType.TEXT,   // 对话框的类型，TEXT是文本框。            // 相机镜头对准NPC
        title: '工程师',
        content: `自从有了这个雷电套装，再也不怕被机械虫炸了，可以放心修城市了~`,
    });
});*/

/*const gq1 = world.querySelector('#npcbc');
gq1.enableInteract = true; // 允许进行互动
gq1.interactRadius = 3;   // 实体的互动范围
gq1.interactHint = '补偿领取' // 互动提示框显示实体的名称

// 玩家与实体进行交互时触发
gq1.onInteract(async ({ entity }) => {
    //if (entity.data.bc = 2) return entity.player.directMessage(`你已经领取过了～`);
    if (entity.data.bc = 0) {
        (async function () {
            let a = ItemList.filter(e => e.rare && e.noBreak && e.type != '臂章' && e.type != '背包');
            let na = a.map(e => e.name);
            let aa = a.map(e => e.id);

            let resulta = await dialog(entity, '补偿奖励', `请在下列礼物中选择一个`, na);
            if (!resulta || resulta == null) {
                return;
            }
            entity.data.item.push(aa[resulta.index]);
            //entity.data.bc += 1
            
        })()
    }
    
});*/


const cs1 = world.querySelector('#星际传送门-1');
cs1.enableInteract = true; // 允许进行互动
cs1.interactRadius = 3;   // 实体的互动范围
cs1.interactHint = '传送到陈列厅' // 互动提示框显示实体的名称

// 玩家与实体进行交互时触发
cs1.onInteract(async ({ entity }) => {
    entity.player.link('https://dao3.fun/play/a4c3699cdffd96efb2b6', { isConfirm: false, isNewTab: false })
});



async function restart(reason = null) {
    const promises = [];
    const players = world.querySelectorAll('player');
    let saved = 0;
    for (let index = 0; index < players.length; index++) {
        const e = players[index];
        const contrib = e.contrib;
        e.gainExp(Math.round(contrib / 1.5));
        e.gainCoin(Math.round(contrib / 1.5));
        e.contrib = 0;
        promises.push(savePlayer(e).then(() => {
            saved++
            world.say(`数据保存成功 ${saved}/${players.length}`);
            e.dlgMsg(`${reason ? reason + "\n" : ""}服务器即将重启\n使用你的贡献值(${contrib}) / 1.5进行结算\n获得【夸克币】${contrib / 1.5}\n获得【经验】${contrib / 1.5}`);
            return Promise.resolve();
        }));
        await sleep(500)
    }

    await Promise.all(promises);
    for (let i = 10; i > 0; i--) {
        world.say(`服务器将在${i}s后重启`);
        await sleep(1000);
    }
    for (; ;) { }
}

//------------��AI玩家专用代码区��------------


async function createAIPlayer(role = 'S', level = 1, position = new GameVector3(128, 128, 128)) {
    let e = world.createEntity({
        mesh: `mesh/${role}AI.vb`,
        collides: true,
        gravity: true,
        meshScale: new GameVector3(0.05, 0.05, 0.05),
        friction: 1,
        position: position,
    });
    e.level = level;
    e.hp = e.maxHp = 100 + e.level * 50;
    e.enableDamage = true;
    e.speed = 0.5 + e.level * 0.3;
    e.addTag('AI');
    e.def = 48 + level * 8;
    e.atk = 18 + level * 2;
    e.dist = 24 + level * 4;//射程
    e.role = role;
    e.name = AINames.pickOne();
    e.enableInteract = true;
    e.interactRadius = 3;
    e.targetPos = e.position.clone();//移动的位置
    e.target = null;
    e.tt = 0;
    e.needNum = e.maxNeedNum = 400 + level * 200;
    while (1) {
        e.tt++
        e.interactHint = `${e.name} 职业：${Role[role].name} 等级：${e.level} 血量❤️：${e.hp}/${e.maxHp}`;
        switch (e.role) {
            case 'S':
                if (e.tt % 3 == 0 && Math.random() < 0.9) {
                    e.target = e.nearestAliveEntities(world.querySelectorAll('.eDrone,.ship'))[0];
                    if (e.target) {
                        if (e.target.position.distance(e.position) < e.dist * 10) {
                            if (e.needNum > 0) {
                                e.target.hurt(e.atk);
                                e.needNum -= 10;
                            }

                        }
                    };
                };
                break;
        };
        await sleep(100)
    }
}




























